org.cloudfoundry.identity.uaa.provider.oauth.XOAuthAuthenticationManagerIT.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.provider.oauth.XOAuthAuthenticationManagerIT.java

Source

/*******************************************************************************
 * Cloud Foundry
 * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 * <p>
 * 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.
 * <p>
 * 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.provider.oauth;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent;
import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent;
import org.cloudfoundry.identity.uaa.cache.ExpiringUrlCache;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.impl.config.RestTemplateConfig;
import org.cloudfoundry.identity.uaa.oauth.*;
import org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey;
import org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKeySet;
import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken;
import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeyResponse;
import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeysListResponse;
import org.cloudfoundry.identity.uaa.provider.*;
import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember;
import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager;
import org.cloudfoundry.identity.uaa.user.*;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.TimeServiceImpl;
import org.cloudfoundry.identity.uaa.util.UaaTokenUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.jwt.crypto.sign.InvalidSignatureException;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ISS;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.util.UaaMapUtils.entry;
import static org.cloudfoundry.identity.uaa.util.UaaMapUtils.map;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.*;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;

public class XOAuthAuthenticationManagerIT {
    public static final String UAA_ORIGIN = "uaa";

    private MockRestServiceServer mockUaaServer;
    private XOAuthAuthenticationManager xoAuthAuthenticationManager;
    private IdentityProviderProvisioning provisioning;
    private InMemoryUaaUserDatabase userDatabase;
    private XOAuthCodeToken xCodeToken;
    private ApplicationEventPublisher publisher;
    private static final String CODE = "the_code";

    private static final String ORIGIN = "the_origin";
    private static final String ISSUER = "cf-app.com";
    private static final String UAA_ISSUER_URL = "http://issuer.url";
    private static final List<String> SCOPES_LIST = Arrays.asList("openid", "some.other.scope", "closedid");

    private Map<String, Object> claims;
    private HashMap<String, Object> attributeMappings;
    private OIDCIdentityProviderDefinition config;
    private RsaSigner signer;
    private Map<String, Object> header;
    private String invalidRsaSigningKey;
    private XOAuthProviderConfigurator xoAuthProviderConfigurator;
    private TokenEndpointBuilder tokenEndpointBuilder;

    private static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n"
            + "MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAcjAgsHEfrUxeTFwQPb17AkZ2Im4SfZdp\n"
            + "Y8Ada9pZfxXz1PZSqv9TPTMAzNx+EkzMk2IMYN+uNm1bfDzaxVdz+QIDAQAB\n" + "-----END PUBLIC KEY-----";

    private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n"
            + "MIIBOQIBAAJAcjAgsHEfrUxeTFwQPb17AkZ2Im4SfZdpY8Ada9pZfxXz1PZSqv9T\n"
            + "PTMAzNx+EkzMk2IMYN+uNm1bfDzaxVdz+QIDAQABAkBoR39y4rw0/QsY3PKQD5xo\n"
            + "hYSZCMCmJUI/sFCuECevIFY4h6q9KBP+4Set96f7Bgs9wJWVvCMx/nJ6guHAjsIB\n"
            + "AiEAywVOoCGIZ2YzARXWYcMRYZ89hxoHh8kZ+QMthRSZieECIQCP/GWQYgyofAQA\n"
            + "BtM8YwThXEV+S3KtuCn4IAQ89gqdGQIgULBASpZpPyc4OEM0nFBKFTGT46EtwwLj\n"
            + "RrvDmLPSPiECICQi9FqIQSUH+vkGvX0qXM8ymT5ZMS7oSaA8aNPj7EYBAiEAx5V3\n"
            + "2JGEulMY3bK1PVGYmtsXF1gq6zbRMoollMCRSMg=\n" + "-----END RSA PRIVATE KEY-----";

    @AfterEach
    public void clearContext() {
        SecurityContextHolder.clearContext();
        IdentityZoneHolder.clear();
    }

    @BeforeEach
    public void setUp() throws Exception {
        RestTemplateConfig restTemplateConfig = new RestTemplateConfig();
        RestTemplate nonTrustingRestTemplate = restTemplateConfig.nonTrustingRestTemplate();
        RestTemplate trustingRestTemplate = restTemplateConfig.trustingRestTemplate();
        SecurityContextHolder.clearContext();
        IdentityZoneHolder.clear();
        String keyName = "testKey";
        header = map(entry("alg", "HS256"), entry("kid", keyName), entry("typ", "JWT"));
        signer = new RsaSigner(PRIVATE_KEY);
        IdentityZoneHolder.get().getConfig().getTokenPolicy()
                .setKeys(Collections.singletonMap(keyName, PRIVATE_KEY));

        provisioning = mock(IdentityProviderProvisioning.class);
        ScimGroupExternalMembershipManager externalMembershipManager = mock(
                ScimGroupExternalMembershipManager.class);

        for (String scope : SCOPES_LIST) {
            ScimGroupExternalMember member = new ScimGroupExternalMember();
            member.setDisplayName(scope);
            when(externalMembershipManager.getExternalGroupMapsByExternalGroup(eq(scope), anyString(), anyString()))
                    .thenReturn(Collections.singletonList(member));
        }

        userDatabase = new InMemoryUaaUserDatabase(Collections.emptySet());
        publisher = mock(ApplicationEventPublisher.class);
        tokenEndpointBuilder = mock(TokenEndpointBuilder.class);
        when(tokenEndpointBuilder.getTokenEndpoint()).thenReturn(UAA_ISSUER_URL);
        OidcMetadataFetcher oidcMetadataFetcher = new OidcMetadataFetcher(
                new ExpiringUrlCache(Duration.ofMinutes(2), new TimeServiceImpl(), 10), trustingRestTemplate,
                nonTrustingRestTemplate);
        xoAuthProviderConfigurator = spy(new XOAuthProviderConfigurator(provisioning, oidcMetadataFetcher));
        xoAuthAuthenticationManager = spy(
                new XOAuthAuthenticationManager(xoAuthProviderConfigurator, trustingRestTemplate,
                        nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_URL)));
        xoAuthAuthenticationManager.setUserDatabase(userDatabase);
        xoAuthAuthenticationManager.setExternalMembershipManager(externalMembershipManager);
        xoAuthAuthenticationManager.setApplicationEventPublisher(publisher);
        xoAuthAuthenticationManager.setTokenEndpointBuilder(tokenEndpointBuilder);
        xCodeToken = new XOAuthCodeToken(CODE, ORIGIN, "http://localhost/callback/the_origin");
        claims = map(entry("sub", "12345"), entry("preferred_username", "marissa"), entry("origin", UAA_ORIGIN),
                entry("iss", "http://localhost/oauth/token"), entry("given_name", "Marissa"),
                entry("client_id", "client"), entry("aud", Arrays.asList("identity", "another_trusted_client")),
                entry("zid", "uaa"), entry("user_id", "12345"), entry("azp", "client"),
                entry("scope", Collections.singletonList("openid")), entry("auth_time", 1458603913),
                entry("phone_number", "1234567890"), entry("exp", Instant.now().getEpochSecond() + 3600),
                entry("iat", 1458603913), entry("family_name", "Bloggs"),
                entry("jti", "b23fe183-158d-4adc-8aff-65c440bbbee1"), entry("email", "marissa@bloggs.com"),
                entry("rev_sig", "3314dc98"), entry("cid", "client"), entry("email_verified", true),
                entry(ClaimConstants.ACR, JsonUtils.readValue(
                        "{\"values\": [\"urn:oasis:names:tc:SAML:2.0:ac:classes:Password\"] }", Map.class)));

        attributeMappings = new HashMap<>();

        config = new OIDCIdentityProviderDefinition().setAuthUrl(new URL("http://localhost/oauth/authorize"))
                .setTokenUrl(new URL("http://localhost/oauth/token")).setIssuer("http://localhost/oauth/token")
                .setShowLinkText(true).setLinkText("My OIDC Provider").setRelyingPartyId("identity")
                .setRelyingPartySecret("identitysecret").setUserInfoUrl(new URL("http://localhost/userinfo"))
                .setTokenKey(PUBLIC_KEY);
        config.setExternalGroupsWhitelist(Collections.singletonList("*"));

        mockUaaServer = MockRestServiceServer.createServer(nonTrustingRestTemplate);

        invalidRsaSigningKey = "-----BEGIN RSA PRIVATE KEY-----\n"
                + "MIIBOgIBAAJBAJnlBG4lLmUiHslsKDODfd0MqmGZRNUOhn7eO3cKobsFljUKzRQe\n"
                + "GB7LYMjPavnKccm6+jWSXutpzfAc9A9wXG8CAwEAAQJADwwdiseH6cuURw2UQLUy\n"
                + "sVJztmdOG6b375+7IMChX6/cgoF0roCPP0Xr70y1J4TXvFhjcwTgm4RI+AUiIDKw\n"
                + "gQIhAPQHwHzdYG1639Qz/TCHzuai0ItwVC1wlqKpat+CaqdZAiEAoXFyS7249mRu\n"
                + "xtwRAvxKMe+eshHvG2le+ZDrM/pz8QcCIQCzmCDpxGL7L7sbCUgFN23l/11Lwdex\n"
                + "uXKjM9wbsnebwQIgeZIbVovUp74zaQ44xT3EhVwC7ebxXnv3qAkIBMk526sCIDVg\n"
                + "z1jr3KEcaq9zjNJd9sKBkqpkVSqj8Mv+Amq+YjBA\n" + "-----END RSA PRIVATE KEY-----";
    }

    @Test
    public void get_response_type_for_oauth2() {
        RawXOAuthIdentityProviderDefinition signed = new RawXOAuthIdentityProviderDefinition();
        signed.setResponseType("signed_request");
        RawXOAuthIdentityProviderDefinition token = new RawXOAuthIdentityProviderDefinition();
        OIDCIdentityProviderDefinition oidcIdentityProviderDefinition = new OIDCIdentityProviderDefinition();

        assertEquals("signed_request", xoAuthAuthenticationManager.getResponseType(signed));
        assertEquals("token", xoAuthAuthenticationManager.getResponseType(token));
        assertEquals("id_token", xoAuthAuthenticationManager.getResponseType(oidcIdentityProviderDefinition));
    }

    @Test
    public void unknown_config_class() {
        assertThrows(IllegalArgumentException.class, () -> {
            xoAuthAuthenticationManager.getResponseType(new AbstractXOAuthIdentityProviderDefinition() {
                @Override
                public URL getAuthUrl() {
                    return super.getAuthUrl();
                }
            });
        }, "Unknown type for provider.");
    }

    @Test
    public void verify_hmac_256_signature() throws Exception {
        String key = "key";
        String data = "data";
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKey);
        byte[] hmacData = mac.doFinal(data.getBytes("UTF-8"));
        assertThat(new String(Base64.encodeBase64URLSafe(hmacData)),
                equalTo(xoAuthAuthenticationManager.hmacSignAndEncode(data, key)));
    }

    @Test
    public void test_authManager_origin_is_thread_safe() throws Exception {
        CountDownLatch countDownLatchA = new CountDownLatch(1);
        CountDownLatch countDownLatchB = new CountDownLatch(1);

        final String[] thread1Origin = new String[1];
        final String[] thread2Origin = new String[1];
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                xoAuthAuthenticationManager.setOrigin("a");
                resumeThread2();
                pauseThread1();
                thread1Origin[0] = xoAuthAuthenticationManager.getOrigin();
            }

            private void resumeThread2() {
                countDownLatchB.countDown();
            }

            private void pauseThread1() {
                try {
                    countDownLatchA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread2 = new Thread() {
            @Override
            public void run() {
                pauseThread2();
                xoAuthAuthenticationManager.setOrigin("b");
                resumeThread1();

                thread2Origin[0] = xoAuthAuthenticationManager.getOrigin();
            }

            private void resumeThread1() {
                countDownLatchA.countDown();
            }

            private void pauseThread2() {
                try {
                    countDownLatchB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        thread2.start();
        thread1.start();

        thread1.join();
        thread2.join();

        assertThat(thread1Origin[0], is("a"));
        assertThat(thread2Origin[0], is("b"));
    }

    @Test
    public void when_a_null_id_token_is_provided_resolveOriginProvider_should_throw_a_jwt_validation_exception() {
        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.resolveOriginProvider(null),
                "Unable to decode expected id_token");
    }

    @Test
    public void unable_to_resolve_to_single_provider() {
        CompositeToken token = getCompositeAccessToken();
        xCodeToken = new XOAuthCodeToken(null, null, null, token.getIdTokenValue(), null, null);
        String zoneId = IdentityZoneHolder.get().getId();
        when(provisioning.retrieveAll(eq(true), eq(zoneId))).thenReturn(emptyList());

        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken),
                String.format("Unable to map issuer, %s , to a single registered provider", claims.get(ISS)));
    }

    @Test
    public void issuer_missing_in_id_token() {
        getProvider();
        CompositeToken token = getCompositeAccessToken(Collections.singletonList(ISS));
        xCodeToken = new XOAuthCodeToken(null, null, null, token.getIdTokenValue(), null, null);

        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken),
                String.format("Issuer is missing in id_token"));
    }

    @Test
    public void origin_is_resolved_based_on_issuer_and_id_token() {
        CompositeToken token = getCompositeAccessToken();
        xCodeToken = new XOAuthCodeToken(null, null, null, token.getIdTokenValue(), null, null);
        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(Collections.singletonList(getProvider()));
        //perform test
        xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken);

        ArgumentCaptor<String> idTokenCaptor = ArgumentCaptor.forClass(String.class);
        verify(xoAuthAuthenticationManager, times(1)).resolveOriginProvider(idTokenCaptor.capture());
        verify(provisioning, never()).retrieveByOrigin(anyString(), anyString());
        verify(xoAuthProviderConfigurator, times(1)).retrieveByIssuer(eq("http://localhost/oauth/token"),
                anyString());
        assertEquals(token.getIdTokenValue(), idTokenCaptor.getValue());
    }

    @Test
    public void when_unable_to_find_an_idp_that_matches_the_id_token_issuer() {

        String issuerURL = "http://issuer.url";
        when(tokenEndpointBuilder.getTokenEndpoint()).thenReturn("http://another-issuer.url");
        claims.put("iss", issuerURL);
        CompositeToken token = getCompositeAccessToken();

        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.resolveOriginProvider(token.getIdTokenValue()));
    }

    @Test
    public void when_exchanging_an_id_token_retrieved_from_the_internal_uaa_idp_for_an_access_token_then_auth_data_should_contain_oidc_sub_claim() {
        when(tokenEndpointBuilder.getIssuer()).thenReturn("http://localhost/oauth/token");
        when(tokenEndpointBuilder.getTokenEndpoint()).thenReturn("http://localhost/oauth/token");

        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(new ArrayList<>());

        String username = RandomStringUtils.random(50);
        claims.put("sub", username);
        claims.put("iss", "http://localhost/oauth/token");
        claims.put("origin", UAA_ORIGIN);

        CompositeToken token = getCompositeAccessToken();
        String idToken = token.getIdTokenValue();
        xCodeToken.setIdToken(idToken);
        xCodeToken.setOrigin(null);

        XOAuthAuthenticationManager.AuthenticationData externalAuthenticationDetails = xoAuthAuthenticationManager
                .getExternalAuthenticationDetails(xCodeToken);

        assertThat(username, is(externalAuthenticationDetails.getUsername()));
        assertThat(externalAuthenticationDetails.getClaims().get(ClaimConstants.ORIGIN), is(UAA_ORIGIN));
        assertThat(xoAuthAuthenticationManager.getOrigin(), is(UAA_ORIGIN));
    }

    @ParameterizedTest
    @MethodSource("invalidOrigins")
    public void when_exchanging_an_id_token_issuedby_the_uaa_idp_but_not_uaa_origin(String origin) {
        when(tokenEndpointBuilder.getIssuer()).thenReturn("http://localhost/oauth/token");
        when(tokenEndpointBuilder.getTokenEndpoint()).thenReturn("http://localhost/oauth/token");

        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(new ArrayList<>());

        String username = RandomStringUtils.random(50);
        claims.put("sub", username);
        claims.put("iss", "http://localhost/oauth/token");
        claims.put("origin", origin);

        CompositeToken token = getCompositeAccessToken();
        String idToken = token.getIdTokenValue();
        xCodeToken.setIdToken(idToken);
        xCodeToken.setOrigin(null);

        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));
    }

    @Test
    public void when_exchanging_an_id_token_retrieved_by_uaa_via_an_oidc_idp_for_an_access_token_origin_should_be_kept() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> idpProvider = getProvider();
        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(Collections.singletonList(idpProvider));

        String username = RandomStringUtils.random(50);
        claims.put("sub", username);
        claims.put("iss", UAA_ISSUER_URL);
        claims.put("origin", idpProvider.getOriginKey());

        CompositeToken token = getCompositeAccessToken();
        String idToken = token.getIdTokenValue();
        xCodeToken.setIdToken(idToken);
        xCodeToken.setOrigin(null);

        XOAuthAuthenticationManager.AuthenticationData externalAuthenticationDetails = xoAuthAuthenticationManager
                .getExternalAuthenticationDetails(xCodeToken);

        assertThat(username, is(externalAuthenticationDetails.getUsername()));
        assertThat(externalAuthenticationDetails.getClaims().get(ClaimConstants.ORIGIN),
                is(idpProvider.getOriginKey()));
        assertThat(xoAuthAuthenticationManager.getOrigin(), is(idpProvider.getOriginKey()));
    }

    @Test
    public void when_exchanging_an_id_token_retrieved_by_uaa_via_an_registered_oidc_idp_for_an_access_token_origin_should_be_taken_from_token() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> idpProvider = getProvider();
        idpProvider.setType(OriginKeys.OIDC10);
        idpProvider.getConfig().setIssuer(UAA_ISSUER_URL);
        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(Collections.singletonList(idpProvider));

        String username = RandomStringUtils.random(50);
        claims.put("sub", username);
        claims.put("iss", UAA_ISSUER_URL);
        claims.put("origin", OriginKeys.UAA);

        CompositeToken token = getCompositeAccessToken();
        String idToken = token.getIdTokenValue();
        xCodeToken.setIdToken(idToken);
        xCodeToken.setOrigin(null);

        XOAuthAuthenticationManager.AuthenticationData externalAuthenticationDetails = xoAuthAuthenticationManager
                .getExternalAuthenticationDetails(xCodeToken);

        assertThat(username, is(externalAuthenticationDetails.getUsername()));
        assertThat(externalAuthenticationDetails.getClaims().get(ClaimConstants.ORIGIN), is(OriginKeys.UAA));
        assertThat(xoAuthAuthenticationManager.getOrigin(), is(idpProvider.getOriginKey()));
    }

    @Test
    public void when_exchanging_an_id_token_retrieved_by_an_external_oidc_idp_for_an_access_token_then_auth_data_should_contain_oidc_sub_claim() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> idpProvider = getProvider();
        when(provisioning.retrieveAll(eq(true), anyString())).thenReturn(Collections.singletonList(idpProvider));

        String username = RandomStringUtils.random(50);
        claims.put("sub", username);
        claims.put("iss", idpProvider.getConfig().getIssuer());
        claims.put("origin", idpProvider.getOriginKey());

        CompositeToken token = getCompositeAccessToken();
        String idToken = token.getIdTokenValue();
        xCodeToken.setIdToken(idToken);
        xCodeToken.setOrigin(null);

        XOAuthAuthenticationManager.AuthenticationData externalAuthenticationDetails = xoAuthAuthenticationManager
                .getExternalAuthenticationDetails(xCodeToken);

        assertThat(username, is(externalAuthenticationDetails.getUsername()));
        assertThat(externalAuthenticationDetails.getClaims().get(ClaimConstants.ORIGIN),
                is(idpProvider.getOriginKey()));
    }

    @Test
    public void discoveryURL_is_used() throws MalformedURLException {
        URL authUrl = config.getAuthUrl();
        URL tokenUrl = config.getTokenUrl();

        config.setAuthUrl(null);
        config.setTokenUrl(null);
        config.setDiscoveryUrl(new URL("http://some.discovery.url"));

        Map<String, Object> discoveryContent = new HashMap();
        discoveryContent.put("authorization_endpoint", authUrl.toString());
        discoveryContent.put("token_endpoint", tokenUrl.toString());
        //mandatory but not used
        discoveryContent.put("userinfo_endpoint", "http://localhost/userinfo");
        discoveryContent.put("jwks_uri", "http://localhost/token_keys");
        discoveryContent.put("issuer", "http://localhost/issuer");

        mockUaaServer.expect(requestTo("http://some.discovery.url")).andRespond(
                withStatus(OK).contentType(APPLICATION_JSON).body(JsonUtils.writeValueAsBytes(discoveryContent)));

        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();
        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

        mockToken();
        addTheUserOnAuth();
        xoAuthAuthenticationManager.authenticate(xCodeToken);
        verify(xoAuthProviderConfigurator, atLeast(1)).overlay(eq(config));
        mockUaaServer.verify();

    }

    @Test
    public void clientAuthInBody_is_used() {
        config.setClientAuthInBody(true);
        mockUaaServer.expect(requestTo(config.getTokenUrl().toString()))
                .andExpect(request -> assertThat("Check Auth header not present",
                        request.getHeaders().get("Authorization"), nullValue()))
                .andExpect(content().string(containsString("client_id=" + config.getRelyingPartyId())))
                .andExpect(content().string(containsString("client_secret=" + config.getRelyingPartySecret())))
                .andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(getIdTokenResponse()));
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();
        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

        xoAuthAuthenticationManager.getClaimsFromToken(xCodeToken, config);

        mockUaaServer.verify();
    }

    @Test
    public void idToken_In_Redirect_Should_Use_it() {
        mockToken();
        addTheUserOnAuth();
        String tokenResponse = getIdTokenResponse();
        String idToken = (String) JsonUtils.readValue(tokenResponse, Map.class).get("id_token");
        xCodeToken.setIdToken(idToken);
        xoAuthAuthenticationManager.authenticate(xCodeToken);

        verify(xoAuthAuthenticationManager, times(1)).getClaimsFromToken(same(xCodeToken), any());
        verify(xoAuthAuthenticationManager, times(1)).getClaimsFromToken(eq(idToken), any());
        verify(xoAuthAuthenticationManager, never()).getRestTemplate(any());

        ArgumentCaptor<ApplicationEvent> userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class);
        verify(publisher, times(3)).publishEvent(userArgumentCaptor.capture());
        assertEquals(3, userArgumentCaptor.getAllValues().size());
        NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent) userArgumentCaptor.getAllValues().get(0);

        assertUserCreated(event);
    }

    @Test
    public void exchangeExternalCodeForIdToken_andCreateShadowUser() {
        mockToken();
        addTheUserOnAuth();

        xoAuthAuthenticationManager.authenticate(xCodeToken);

        mockUaaServer.verify();

        ArgumentCaptor<ApplicationEvent> userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class);
        verify(publisher, times(3)).publishEvent(userArgumentCaptor.capture());
        assertEquals(3, userArgumentCaptor.getAllValues().size());
        NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent) userArgumentCaptor.getAllValues().get(0);

        assertUserCreated(event);
    }

    @Test
    public void test_single_key_response() throws Exception {
        configureTokenKeyResponse("http://localhost/token_key", PRIVATE_KEY, "correctKey", false);
        addTheUserOnAuth();
        xoAuthAuthenticationManager.authenticate(xCodeToken);
    }

    @Test
    public void test_single_key_response_without_value() throws Exception {
        String json = getKeyJson(PRIVATE_KEY, "correctKey", false);
        Map<String, Object> map = JsonUtils.readValue(json, new TypeReference<Map<String, Object>>() {
        });
        map.remove("value");
        json = JsonUtils.writeValueAsString(map);
        configureTokenKeyResponse("http://localhost/token_key", json);
        addTheUserOnAuth();
        xoAuthAuthenticationManager.authenticate(xCodeToken);
    }

    @Test
    public void test_multi_key_response_without_value() throws Exception {
        String jsonValid = getKeyJson(PRIVATE_KEY, "correctKey", false);
        String jsonInvalid = getKeyJson(invalidRsaSigningKey, "invalidKey", false);
        Map<String, Object> mapValid = JsonUtils.readValue(jsonValid, new TypeReference<Map<String, Object>>() {
        });
        Map<String, Object> mapInvalid = JsonUtils.readValue(jsonInvalid, new TypeReference<Map<String, Object>>() {
        });
        mapValid.remove("value");
        mapInvalid.remove("value");
        String json = JsonUtils.writeValueAsString(
                new JsonWebKeySet<>(Arrays.asList(new JsonWebKey(mapInvalid), new JsonWebKey(mapValid))));
        configureTokenKeyResponse("http://localhost/token_key", json);
        addTheUserOnAuth();
        xoAuthAuthenticationManager.authenticate(xCodeToken);
    }

    @Test
    public void test_multi_key_all_invalid() throws Exception {
        String jsonInvalid = getKeyJson(invalidRsaSigningKey, "invalidKey", false);
        String jsonInvalid2 = getKeyJson(invalidRsaSigningKey, "invalidKey2", false);
        Map<String, Object> mapInvalid = JsonUtils.readValue(jsonInvalid, new TypeReference<Map<String, Object>>() {
        });
        Map<String, Object> mapInvalid2 = JsonUtils.readValue(jsonInvalid2,
                new TypeReference<Map<String, Object>>() {
                });
        String json = JsonUtils.writeValueAsString(
                new JsonWebKeySet<>(Arrays.asList(new JsonWebKey(mapInvalid), new JsonWebKey(mapInvalid2))));
        assertTrue(json.contains("\"invalidKey\""));
        assertTrue(json.contains("\"invalidKey2\""));
        configureTokenKeyResponse("http://localhost/token_key", json);
        addTheUserOnAuth();
        try {
            xoAuthAuthenticationManager.authenticate(xCodeToken);
            fail("not expected");
        } catch (Exception e) {
            assertTrue(e.getCause() instanceof InvalidSignatureException);
        }
    }

    @Test
    public void test_multi_key_response() throws Exception {
        configureTokenKeyResponse("http://localhost/token_key", PRIVATE_KEY, "correctKey", true);
        addTheUserOnAuth();
        xoAuthAuthenticationManager.authenticate(xCodeToken);
    }

    @Test
    public void doesNotCreateShadowUserAndFailsAuthentication_IfAddShadowUserOnLoginIsFalse() {
        config.setAddShadowUserOnLogin(false);
        mockToken();

        assertThrows(AccountNotPreCreatedException.class,
                () -> xoAuthAuthenticationManager.authenticate(xCodeToken));

    }

    @Test
    public void rejectTokenWithInvalidSignature() {
        mockToken();

        config.setTokenKey("WRONG_KEY");

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void rejectTokenWithInvalidSignatureAccordingToTokenKeyEndpoint() throws Exception {
        configureTokenKeyResponse("http://localhost/token_key", invalidRsaSigningKey, "wrongKey");

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void rejectTokenWithInvalidIssuer() {
        claims.put("iss", "http://wrong.issuer/");
        mockToken();

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void rejectExpiredToken() {
        claims.put("exp", Instant.now().getEpochSecond() - 1);
        mockToken();

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void rejectWrongAudience() {
        claims.put("aud", Arrays.asList("another_client", "a_complete_stranger"));
        mockToken();

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void updateShadowUser_IfAlreadyExists() {
        claims.put("scope", SCOPES_LIST);
        attributeMappings.put(GROUP_ATTRIBUTE_NAME, "scope");
        mockToken();

        UaaUser existingShadowUser = new UaaUser(
                new UaaUserPrototype().withUsername("12345").withPassword("").withEmail("marissa_old@bloggs.com")
                        .withGivenName("Marissa_Old").withFamilyName("Bloggs_Old").withId("user-id")
                        .withOrigin("the_origin").withZoneId("uaa").withAuthorities(UaaAuthority.USER_AUTHORITIES));

        userDatabase.addUser(existingShadowUser);

        xoAuthAuthenticationManager.authenticate(xCodeToken);
        mockUaaServer.verify();

        ArgumentCaptor<ApplicationEvent> userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class);
        verify(publisher, times(2)).publishEvent(userArgumentCaptor.capture());
        assertEquals(2, userArgumentCaptor.getAllValues().size());
        ExternalGroupAuthorizationEvent event = (ExternalGroupAuthorizationEvent) userArgumentCaptor.getAllValues()
                .get(0);

        UaaUser uaaUser = event.getUser();
        assertEquals("Marissa", uaaUser.getGivenName());
        assertEquals("Bloggs", uaaUser.getFamilyName());
        assertEquals("marissa@bloggs.com", uaaUser.getEmail());
        assertEquals("the_origin", uaaUser.getOrigin());
        assertEquals("1234567890", uaaUser.getPhoneNumber());
        assertEquals("12345", uaaUser.getUsername());
        assertEquals(OriginKeys.UAA, uaaUser.getZoneId());
    }

    @Test
    public void invitedUser_becomesVerifiedOnAccept() {
        setUpInvitedUser();

        claims.remove("preferred_username");
        claims.put("preferred_username", "marissa@bloggs.com");
        mockToken();

        xoAuthAuthenticationManager.authenticate(xCodeToken);
        mockUaaServer.verify();

        ArgumentCaptor<ApplicationEvent> userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class);
        verify(publisher, times(3)).publishEvent(userArgumentCaptor.capture());
        assertEquals(3, userArgumentCaptor.getAllValues().size());
        assertThat(userArgumentCaptor.getAllValues().get(0), instanceOf(InvitedUserAuthenticatedEvent.class));

        RequestContextHolder.resetRequestAttributes();
    }

    @Test
    public void loginAndValidateSignatureUsingTokenKeyEndpoint() throws Exception {
        config.setTokenKeyUrl(new URL("http://localhost/token_key"));
        config.setTokenKey(null);

        KeyInfo key = KeyInfoBuilder.build("correctKey", PRIVATE_KEY, UAA_ISSUER_URL);
        VerificationKeyResponse verificationKeyResponse = TokenKeyEndpoint.getVerificationKeyResponse(key);
        String response = JsonUtils.writeValueAsString(verificationKeyResponse);

        mockToken();
        mockUaaServer.expect(requestTo("http://localhost/token_key"))
                .andExpect(header("Authorization",
                        "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
                .andExpect(header("Accept", "application/json"))
                .andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response));

        mockToken();

        UaaUser existingShadowUser = new UaaUser(
                new UaaUserPrototype().withUsername("12345").withPassword("").withEmail("marissa_old@bloggs.com")
                        .withGivenName("Marissa_Old").withFamilyName("Bloggs_Old").withId("user-id")
                        .withOrigin("the_origin").withZoneId("uaa").withAuthorities(UaaAuthority.USER_AUTHORITIES));

        userDatabase.addUser(existingShadowUser);

        xoAuthAuthenticationManager.authenticate(xCodeToken);
    }

    @Test
    public void authenticatedUser_hasAuthoritiesFromListOfIDTokenRoles() {
        claims.put("scope", SCOPES_LIST);
        config.setExternalGroupsWhitelist(Collections.emptyList());
        testTokenHasAuthoritiesFromIdTokenRoles();
    }

    @Test
    public void authenticatedUser_hasAuthoritiesFromCommaSeparatedStringOfIDTokenRoles() {
        claims.put("scope", "openid,some.other.scope,closedid");
        testTokenHasAuthoritiesFromIdTokenRoles();
    }

    @Test
    public void authenticatedUser_hasConfigurableUsernameField() {
        attributeMappings.put(USER_NAME_ATTRIBUTE_NAME, "username");

        claims.remove("preferred_username");
        claims.put("username", "marissa");
        mockToken();

        UaaUser uaaUser = xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));

        assertThat(uaaUser.getUsername(), is("marissa"));
    }

    @Test
    public void username_defaults_to_subject() {
        claims.remove("preferred_username");
        mockToken();
        UaaUser uaaUser = xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));
        assertThat(uaaUser.getUsername(), is("12345"));
    }

    @Test
    public void missing_user_name_throws_auth_exception() {
        claims.remove("preferred_username");
        claims.remove("sub");
        mockToken();

        assertThrows(InsufficientAuthenticationException.class,
                () -> xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken),
                String.format("Unable to map claim to a username"));
    }

    @Test
    public void getUserWithNullEmail() {
        claims.put("email", null);
        mockToken();
        UaaUser user = xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));

        assertEquals("12345@user.from.the_origin.cf", user.getEmail());
    }

    @Test
    public void testGetUserSetsTheRightOrigin() {
        xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));
        assertEquals(ORIGIN, xoAuthAuthenticationManager.getOrigin());

        XOAuthCodeToken otherToken = new XOAuthCodeToken(CODE, "other_origin",
                "http://localhost/callback/the_origin");
        xoAuthAuthenticationManager.getUser(otherToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(otherToken));
        assertEquals("other_origin", xoAuthAuthenticationManager.getOrigin());
    }

    @Test
    public void testGetUserIssuerOverrideNotUsed() {
        mockToken();
        assertNotNull(xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken)));
    }

    @Test
    public void testGetUserIssuerOverrideUsedNoMatch() {
        config.setIssuer(ISSUER);
        mockToken();

        assertThrows(InvalidTokenException.class, () -> xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken)));
    }

    @Test
    public void testGetUserIssuerOverrideUsedMatch() {
        config.setIssuer(ISSUER);
        claims.remove("iss");
        claims.put("iss", ISSUER);
        mockToken();
        assertNotNull(xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken)));
    }

    @Test
    public void test_authentication_context_transfers_to_authentication() {
        addTheUserOnAuth();
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        assertNotNull(authentication);
        assertNotNull(authentication.getAuthContextClassRef());
        assertThat(authentication.getAuthContextClassRef(),
                containsInAnyOrder("urn:oasis:names:tc:SAML:2.0:ac:classes:Password"));
    }

    @Test
    public void test_authentication_context_when_missing() {
        addTheUserOnAuth();
        claims.remove(ClaimConstants.ACR);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        assertNotNull(authentication);
        assertNull(authentication.getAuthContextClassRef());
    }

    @Test
    public void unableToAuthenticate_whenProviderIsNotOIDCOrOAuth() {
        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString()))
                .thenReturn(MultitenancyFixture.identityProvider("the_origin", "uaa"));
        Authentication authentication = xoAuthAuthenticationManager.authenticate(xCodeToken);
        assertNull(authentication);
    }

    @Test
    public void unableToAuthenticate_whenProviderIsNotFound() {
        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(null);
        Authentication authentication = xoAuthAuthenticationManager.authenticate(xCodeToken);
        assertNull(authentication);
    }

    @Test
    public void tokenCannotBeFetchedFromCodeBecauseOfServerError() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();

        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

        mockUaaServer.expect(requestTo("http://localhost/oauth/token")).andRespond(withServerError());

        assertThrows(HttpServerErrorException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void tokenCannotBeFetchedFromInvalidCode() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();

        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

        mockUaaServer.expect(requestTo("http://localhost/oauth/token")).andRespond(withBadRequest());

        assertThrows(HttpClientErrorException.class, () -> xoAuthAuthenticationManager.authenticate(xCodeToken));
    }

    @Test
    public void authenticationContainsAMRClaim_fromExternalOIDCProvider() {
        addTheUserOnAuth();
        claims.put("amr", Arrays.asList("mfa", "rba"));
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        assertThat(authentication.getAuthenticationMethods(), containsInAnyOrder("mfa", "rba", "ext"));
    }

    @Test
    public void test_user_existing_attributes_mapping() {
        addTheUserOnAuth();

        claims.put("emailClaim", "test@email.org");
        claims.put("firstName", "first_name");
        claims.put("lastName", "last_name");
        claims.put("phoneNum", "randomNumber");
        attributeMappings.put("email", "emailClaim");
        attributeMappings.put("given_name", "firstName");
        attributeMappings.put("family_name", "lastName");
        attributeMappings.put("phone_number", "phoneNum");
        attributeMappings.put("email_verified", "email_verified");
        config.setStoreCustomAttributes(true);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        UaaUser actualUaaUser = xoAuthAuthenticationManager.getUserDatabase()
                .retrieveUserById(authentication.getPrincipal().getId());
        assertEquals("test@email.org", actualUaaUser.getEmail());
        assertEquals("first_name", actualUaaUser.getGivenName());
        assertEquals("last_name", actualUaaUser.getFamilyName());
        assertEquals("randomNumber", actualUaaUser.getPhoneNumber());
        assertTrue("verified", actualUaaUser.isVerified());
    }

    @Test
    public void email_verified_is_false() {
        addTheUserOnAuth();
        claims.put("email_verified", false);
        attributeMappings.put("email_verified", "email_verified");
        config.setStoreCustomAttributes(true);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        UaaUser actualUaaUser = xoAuthAuthenticationManager.getUserDatabase()
                .retrieveUserById(authentication.getPrincipal().getId());
        assertFalse("verified", actualUaaUser.isVerified());
    }

    @Test
    public void email_verified_claim_is_using_a_custom_name() {
        addTheUserOnAuth();
        claims.remove("email_verified");
        claims.put("emailVerified", true);
        attributeMappings.put("email_verified", "emailVerified");
        config.setStoreCustomAttributes(true);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        UaaUser actualUaaUser = xoAuthAuthenticationManager.getUserDatabase()
                .retrieveUserById(authentication.getPrincipal().getId());
        assertTrue("verified", actualUaaUser.isVerified());
    }

    @Test
    public void email_verified_mapping_is_not_there() {
        addTheUserOnAuth();
        attributeMappings.remove("email_verified");
        config.setStoreCustomAttributes(true);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        UaaUser actualUaaUser = xoAuthAuthenticationManager.getUserDatabase()
                .retrieveUserById(authentication.getPrincipal().getId());
        assertTrue("verified", actualUaaUser.isVerified());
    }

    @Test
    public void email_verified_is_ommitted() {
        addTheUserOnAuth();
        claims.remove("email_verified");
        attributeMappings.put("email_verified", "email_verified");
        config.setStoreCustomAttributes(true);
        mockToken();
        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);
        UaaUser actualUaaUser = xoAuthAuthenticationManager.getUserDatabase()
                .retrieveUserById(authentication.getPrincipal().getId());
        assertFalse("verified", actualUaaUser.isVerified());
    }

    @Test
    public void test_custom_user_attributes_are_stored() {
        addTheUserOnAuth();

        List<String> managers = Arrays.asList("Sue the Sloth", "Kari the AntEater");
        List<String> costCenter = Collections.singletonList("Austin, TX");
        claims.put("managers", managers);
        claims.put("employeeCostCenter", costCenter);
        attributeMappings.put("user.attribute.costCenter", "employeeCostCenter");
        attributeMappings.put("user.attribute.terribleBosses", "managers");
        config.setStoreCustomAttributes(true);
        config.setExternalGroupsWhitelist(Collections.singletonList("*"));
        List<String> scopes = SCOPES_LIST;
        claims.put("scope", scopes);
        attributeMappings.put(GROUP_ATTRIBUTE_NAME, "scope");
        mockToken();
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.put("costCenter", costCenter);
        map.put("terribleBosses", managers);

        UaaAuthentication authentication = (UaaAuthentication) xoAuthAuthenticationManager.authenticate(xCodeToken);

        assertEquals(map, authentication.getUserAttributes());
        assertThat(authentication.getExternalGroups(), containsInAnyOrder(scopes.toArray()));
        UserInfo info = new UserInfo().setUserAttributes(map).setRoles(scopes);
        UserInfo actualUserInfo = xoAuthAuthenticationManager.getUserDatabase()
                .getUserInfo(authentication.getPrincipal().getId());
        assertEquals(actualUserInfo.getUserAttributes(), info.getUserAttributes());
        assertThat(actualUserInfo.getRoles(), containsInAnyOrder(info.getRoles().toArray()));

        UaaUser actualUser = xoAuthAuthenticationManager.getUserDatabase().retrieveUserByName("12345",
                "the_origin");
        assertThat(actualUser, is(not(nullValue())));
        assertThat(actualUser.getGivenName(), is("Marissa"));
    }

    private void assertUserCreated(NewUserAuthenticatedEvent event) {
        assertNotNull(event);
        UaaUser uaaUser = event.getUser();
        assertNotNull(uaaUser);
        assertEquals("Marissa", uaaUser.getGivenName());
        assertEquals("Bloggs", uaaUser.getFamilyName());
        assertEquals("marissa@bloggs.com", uaaUser.getEmail());
        assertEquals("the_origin", uaaUser.getOrigin());
        assertEquals("1234567890", uaaUser.getPhoneNumber());
        assertEquals("12345", uaaUser.getUsername());
        assertEquals(OriginKeys.UAA, uaaUser.getZoneId());
    }

    private void configureTokenKeyResponse(String keyUrl, String signingKey, String keyId)
            throws MalformedURLException {
        configureTokenKeyResponse(keyUrl, signingKey, keyId, false);
    }

    private void configureTokenKeyResponse(String keyUrl, String signingKey, String keyId, boolean list)
            throws MalformedURLException {
        String response = getKeyJson(signingKey, keyId, list);
        configureTokenKeyResponse(keyUrl, response);
    }

    private String getKeyJson(String signingKey, String keyId, boolean list) {
        KeyInfo key = KeyInfoBuilder.build(keyId, signingKey, UAA_ISSUER_URL);
        VerificationKeyResponse keyResponse = TokenKeyEndpoint.getVerificationKeyResponse(key);
        Object verificationKeyResponse = list
                ? new VerificationKeysListResponse(Collections.singletonList(keyResponse))
                : keyResponse;
        return JsonUtils.writeValueAsString(verificationKeyResponse);
    }

    private void configureTokenKeyResponse(String keyUrl, String response) throws MalformedURLException {
        config.setTokenKey(null);
        config.setTokenKeyUrl(new URL(keyUrl));
        mockToken();
        mockUaaServer.expect(requestTo(keyUrl))
                .andExpect(header("Authorization",
                        "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
                .andExpect(header("Accept", "application/json"))
                .andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response));
    }

    private void addTheUserOnAuth() {
        doAnswer(invocation -> {
            Object e = invocation.getArguments()[0];
            if (e instanceof NewUserAuthenticatedEvent) {
                NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent) e;
                UaaUser user = event.getUser();
                userDatabase.addUser(user);
            }
            return null;
        }).when(publisher).publishEvent(any(ApplicationEvent.class));
    }

    private void setUpInvitedUser() {
        UaaUser existingShadowUser = new UaaUser(new UaaUserPrototype().withUsername("marissa@bloggs.com")
                .withPassword("").withEmail("marissa@bloggs.com").withGivenName("Marissa_Old")
                .withFamilyName("Bloggs_Old").withId("user-id").withOrigin("the_origin").withZoneId("uaa")
                .withAuthorities(UaaAuthority.USER_AUTHORITIES));

        userDatabase.addUser(existingShadowUser);

        RequestAttributes attributes = new ServletRequestAttributes(new MockHttpServletRequest());
        attributes.setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION);
        attributes.setAttribute("user_id", existingShadowUser.getId(), RequestAttributes.SCOPE_SESSION);
        RequestContextHolder.setRequestAttributes(attributes);
    }

    private void mockToken() {
        String response = getIdTokenResponse();
        mockUaaServer.expect(requestTo("http://localhost/oauth/token"))
                .andExpect(header("Authorization",
                        "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
                .andExpect(header("Accept", "application/json"))
                .andExpect(content().string(containsString("grant_type=authorization_code")))
                .andExpect(content().string(containsString("code=the_code")))
                .andExpect(content()
                        .string(containsString("redirect_uri=http%3A%2F%2Flocalhost%2Fcallback%2Fthe_origin")))
                .andExpect(content().string(containsString(("response_type=id_token"))))
                .andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response));
    }

    private CompositeToken getCompositeAccessToken() {
        return getCompositeAccessToken(emptyList());
    }

    private CompositeToken getCompositeAccessToken(List<String> removeClaims) {
        removeClaims.stream().forEach(c -> claims.remove(c));
        String idTokenJwt = UaaTokenUtils.constructToken(header, claims, signer);

        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = getProvider();
        when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);

        CompositeToken compositeToken = new CompositeToken("accessToken");
        compositeToken.setIdTokenValue(idTokenJwt);
        return compositeToken;
    }

    private String getIdTokenResponse() {
        return JsonUtils.writeValueAsString(getCompositeAccessToken());
    }

    private IdentityProvider<AbstractXOAuthIdentityProviderDefinition> getProvider() {
        IdentityProvider<AbstractXOAuthIdentityProviderDefinition> identityProvider = new IdentityProvider<>();
        identityProvider.setName("my oidc provider");
        identityProvider.setIdentityZoneId(OriginKeys.UAA);
        config.setAttributeMappings(attributeMappings);

        identityProvider.setConfig(config);
        identityProvider.setOriginKey("puppy");
        return identityProvider;
    }

    private void testTokenHasAuthoritiesFromIdTokenRoles() {
        attributeMappings.put(GROUP_ATTRIBUTE_NAME, "scope");
        mockToken();

        UaaUser uaaUser = xoAuthAuthenticationManager.getUser(xCodeToken,
                xoAuthAuthenticationManager.getExternalAuthenticationDetails(xCodeToken));

        List<String> authorities = uaaUser.getAuthorities().stream().map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        for (String scope : SCOPES_LIST) {
            assertThat(authorities, hasItem(scope));
        }
    }

    private static Stream<String> invalidOrigins() {
        return Stream.of("", null);
    }
}