org.cloudfoundry.identity.uaa.integration.OpenIdTokenAuthorizationWithApprovalIntegrationTests.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.integration.OpenIdTokenAuthorizationWithApprovalIntegrationTests.java

Source

/*******************************************************************************
 *     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.integration;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
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.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration;
import org.springframework.security.oauth2.client.test.OAuth2ContextSetup;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.common.AuthenticationScheme;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriUtils;

/**
 * @author Luke Taylor
 * @author Dave Syer
 */
@OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class)
public class OpenIdTokenAuthorizationWithApprovalIntegrationTests {

    private final String userEndpoint = "/Users";

    @Rule
    public ServerRunning serverRunning = ServerRunning.isRunning();

    private UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning);

    @Rule
    public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccounts);

    @Rule
    public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts);

    private RestTemplate client;

    private RestTemplate loginClient;

    private ScimUser user;

    @Before
    public void createRestTemplate() throws Exception {

        ClientCredentialsResourceDetails clientCredentials = getClientCredentialsResource(
                new String[] { "oauth.login" }, "login", "loginsecret");
        loginClient = new OAuth2RestTemplate(clientCredentials);
        loginClient.setRequestFactory(new StatelessRequestFactory());
        loginClient.setErrorHandler(new OAuth2ErrorHandler(clientCredentials) {
            // Pass errors through in response entity for status code analysis
            @Override
            public boolean hasError(ClientHttpResponse response) throws IOException {
                return false;
            }

            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
            }
        });

        Assume.assumeTrue(!testAccounts.isProfileActive("vcap"));

        client = (RestTemplate) serverRunning.getRestTemplate();
        client.setErrorHandler(new OAuth2ErrorHandler(context.getResource()) {
            // Pass errors through in response entity for status code analysis
            @Override
            public boolean hasError(ClientHttpResponse response) throws IOException {
                return false;
            }

            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
            }
        });
        user = createUser(new RandomValueStringGenerator().generate(), "openiduser", "openidlast",
                "test@openid,com", true).getBody();
    }

    @Test
    public void testOpenIdTokenUsingLoginClientOauthTokenEndpoint() throws Exception {

        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        LinkedMultiValueMap<String, String> postBody = new LinkedMultiValueMap<>();
        postBody.add("client_id", "app");
        postBody.add("client_secret", "appclientsecret");
        postBody.add("redirect_uri", "https://uaa.cloudfoundry.com/redirect/cf");
        postBody.add("response_type", "token id_token");
        postBody.add("grant_type", "password");
        postBody.add("source", "login");
        postBody.add("user_id", user.getId());
        postBody.add("add_new", "false");

        ResponseEntity<Map> responseEntity = loginClient.exchange(serverRunning.getBaseUrl() + "/oauth/token",
                HttpMethod.POST, new HttpEntity<>(postBody, headers), Map.class);

        Assert.assertEquals(HttpStatus.OK, responseEntity.getStatusCode());

        Map<String, Object> params = responseEntity.getBody();

        Assert.assertTrue(params.get("jti") != null);
        Assert.assertEquals("bearer", params.get("token_type"));
        Assert.assertThat((Integer) params.get("expires_in"), Matchers.greaterThan(40000));

        String[] scopes = UriUtils.decode((String) params.get("scope"), "UTF-8").split(" ");
        Assert.assertThat(Arrays.asList(scopes), containsInAnyOrder("scim.userids", "password.write",
                "cloud_controller.write", "openid", "cloud_controller.read"));
    }

    @Test
    public void testOpenIdHybridFlowIdTokenAndCode() throws Exception {
        //non approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("token", "code")),
                ".+access_token=.+code=.+");
        //approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("token", "code")),
                ".+access_token=.+code=.+");
        //approved using login client
        doOpenIdHybridFlowForLoginClient(new HashSet<>(Arrays.asList("token", "code")), ".+access_token=.+code=.+");
    }

    @Test
    public void testOpenIdHybridFlowIdTokenAndTokenAndCode() throws Exception {
        //non approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("token", "id_token", "code")),
                ".+access_token=.+id_token=.+code=.+");
        //approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("token", "id_token", "code")),
                ".+access_token=.+id_token=.+code=.+");
        //approved using login client
        doOpenIdHybridFlowForLoginClient(new HashSet<>(Arrays.asList("token", "id_token", "code")),
                ".+access_token=.+id_token=.+code=.+");
    }

    @Test
    public void testOpenIdHybridFlowIdTokenAndToken() throws Exception {
        //non approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("id_token", "code")),
                ".+id_token=.+code=.+");
        //approved
        doOpenIdHybridFlowIdTokenAndReturnCode(new HashSet<>(Arrays.asList("id_token", "code")),
                ".+id_token=.+code=.+");
        //approved using login client
        doOpenIdHybridFlowForLoginClient(new HashSet<>(Arrays.asList("id_token", "code")), ".+id_token=.+code=.+");
    }

    private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes, String responseTypeMatcher)
            throws Exception {

        HttpHeaders headers = new HttpHeaders();
        // TODO: should be able to handle just TEXT_HTML
        headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));

        AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource();

        StringBuilder responseType = new StringBuilder();
        Iterator<String> rTypes = responseTypes.iterator();
        while (rTypes.hasNext()) {
            String type = rTypes.next();
            responseType.append(type);
            if (rTypes.hasNext()) {
                responseType.append(" ");
            }
        }
        String state = new RandomValueStringGenerator().generate();
        String clientId = resource.getClientId();
        String redirectUri = resource.getPreEstablishedRedirectUri();
        String clientSecret = resource.getClientSecret();
        String uri = serverRunning.getUrl("/oauth/authorize?response_type={response_type}&"
                + "state={state}&client_id={client_id}&redirect_uri={redirect_uri}");

        ResponseEntity<Void> result = serverRunning.getForResponse(uri, headers, responseType, state, clientId,
                redirectUri);
        assertEquals(HttpStatus.FOUND, result.getStatusCode());
        String location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");

        if (result.getHeaders().containsKey("Set-Cookie")) {
            String cookie = result.getHeaders().getFirst("Set-Cookie");
            headers.set("Cookie", cookie);
        }

        ResponseEntity<String> response = serverRunning.getForString(location, headers);
        // should be directed to the login screen...
        assertTrue(response.getBody().contains("/login.do"));
        assertTrue(response.getBody().contains("username"));
        assertTrue(response.getBody().contains("password"));

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
        formData.add("username", user.getUserName());
        formData.add("password", "secret");

        // Should be redirected to the original URL, but now authenticated
        result = serverRunning.postForResponse("/login.do", headers, formData);
        assertEquals(HttpStatus.FOUND, result.getStatusCode());

        if (result.getHeaders().containsKey("Set-Cookie")) {
            String cookie = result.getHeaders().getFirst("Set-Cookie");
            headers.set("Cookie", cookie);
        }

        location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
        response = serverRunning.getForString(location, headers);
        if (response.getStatusCode() == HttpStatus.OK) {
            // The grant access page should be returned
            assertTrue(response.getBody().contains("Application Authorization</h1>"));

            formData.clear();
            formData.add("user_oauth_approval", "true");
            result = serverRunning.postForResponse("/oauth/authorize", headers, formData);
            assertEquals(HttpStatus.FOUND, result.getStatusCode());
            location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
        } else {
            // Token cached so no need for second approval
            assertEquals(HttpStatus.FOUND, response.getStatusCode());
            location = UriUtils.decode(response.getHeaders().getLocation().toString(), "UTF-8");
        }
        assertTrue("Wrong location: " + location,
                location.matches(resource.getPreEstablishedRedirectUri() + responseTypeMatcher.toString()));

        String code = location.split("code=")[1].split("&")[0];
        exchangeCodeForToken(clientId, redirectUri, clientSecret, code, formData);
        return code;
    }

    private void doOpenIdHybridFlowForLoginClient(Set<String> responseTypes, String responseTypeMatcher)
            throws Exception {

        HttpHeaders headers = new HttpHeaders();
        // TODO: should be able to handle just TEXT_HTML
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.ALL));

        AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource();

        StringBuilder responseType = new StringBuilder();
        Iterator<String> rTypes = responseTypes.iterator();
        while (rTypes.hasNext()) {
            String type = rTypes.next();
            responseType.append(type);
            if (rTypes.hasNext()) {
                responseType.append(" ");
            }
        }
        String state = new RandomValueStringGenerator().generate();
        String clientId = resource.getClientId();
        String redirectUri = resource.getPreEstablishedRedirectUri();
        String clientSecret = resource.getClientSecret();
        String uri = serverRunning.getUrl("/oauth/authorize?response_type={response_type}&"
                + "state={state}&client_id={client_id}&client_secret={clientSecret}&redirect_uri={redirect_uri}&source=login&user_id={userId}&add_new=false");

        HttpEntity<Void> request = new HttpEntity<>(null, headers);
        ResponseEntity<Map> result = loginClient.exchange(serverRunning.getUrl(uri), HttpMethod.POST, request,
                Map.class, responseType, state, clientId, clientSecret, redirectUri, user.getId());

        assertEquals(HttpStatus.FOUND, result.getStatusCode());
        String location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
        assertTrue("Wrong location: " + location,
                location.matches(resource.getPreEstablishedRedirectUri() + responseTypeMatcher.toString()));

    }

    private void exchangeCodeForToken(String clientId, String redirectUri, String clientSecret, String value,
            MultiValueMap<String, String> formData) {
        formData.clear();
        formData.add("client_id", clientId);
        formData.add("redirect_uri", redirectUri);
        formData.add("grant_type", "authorization_code");
        formData.add("code", value);
        HttpHeaders tokenHeaders = new HttpHeaders();
        tokenHeaders.set("Authorization", testAccounts.getAuthorizationHeader(clientId, clientSecret));
        @SuppressWarnings("rawtypes")
        ResponseEntity<Map> tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders);
        assertEquals(HttpStatus.OK, tokenResponse.getStatusCode());
        @SuppressWarnings("unchecked")
        Map<String, String> body = tokenResponse.getBody();
        Jwt token = JwtHelper.decode(body.get("access_token"));
        assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"aud\""));
        assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"user_id\""));
    }

    private ResponseEntity<ScimUser> createUser(String username, String firstName, String lastName, String email,
            boolean verified) {
        ScimUser user = new ScimUser();
        user.setUserName(username);
        user.setName(new ScimUser.Name(firstName, lastName));
        user.addEmail(email);
        user.setVerified(verified);
        user.setActive(true);
        user.setPassword("secret");

        return client.postForEntity(serverRunning.getUrl(userEndpoint), user, ScimUser.class);
    }

    private ClientCredentialsResourceDetails getClientCredentialsResource(String[] scope, String clientId,
            String clientSecret) {
        ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
        resource.setClientId(clientId);
        resource.setClientSecret(clientSecret);
        resource.setId(clientId);
        if (scope != null) {
            resource.setScope(Arrays.asList(scope));
        }
        resource.setClientAuthenticationScheme(AuthenticationScheme.header);
        resource.setAccessTokenUri(serverRunning.getBaseUrl() + "/oauth/token");
        return resource;
    }

    private static class StatelessRequestFactory extends HttpComponentsClientHttpRequestFactory {
        @Override
        public HttpClient getHttpClient() {
            return HttpClientBuilder.create().useSystemProperties().disableRedirectHandling()
                    .disableCookieManagement().build();
        }
    }

}