<?php

/**
 * Copyright 2015 OpenStack Foundation
 * 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.
 **/
use auth\User;
use Illuminate\Support\Facades\App;
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
use jwe\IJWE;
use jwk\impl\OctetSequenceJWKFactory;
use jwk\impl\OctetSequenceJWKSpecification;
use jwk\impl\RSAJWKFactory;
use jwk\impl\RSAJWKPEMPrivateKeySpecification;
use jwk\JSONWebKeyPublicKeyUseValues;
use jws\IJWS;
use jws\impl\specs\JWS_ParamsSpecification;
use jws\JWSFactory;
use jwt\RegisteredJWTClaimNames;
use jwt\utils\JWTClaimSetFactory;
use oauth2\models\ClientAssertionAuthenticationContext;
use oauth2\OAuth2Protocol;
use utils\factories\BasicJWTFactory;
use utils\json_types\StringOrURI;
use utils\services\IAuthService;
use utils\services\UtilsServiceCatalog;
use jwt\impl\UnsecuredJWT;
/**
 * Class OIDCProtocolTest
 * http://openid.net/wordpress-content/uploads/2015/02/OpenID-Connect-Conformance-Profiles.pdf
 */
class OIDCProtocolTest extends OpenStackIDBaseTest
{
    /**
     * @var string
     */
    private $current_realm;

    protected function prepareForTests()
    {
        parent::prepareForTests();
        App::singleton(UtilsServiceCatalog::ServerConfigurationService, 'StubServerConfigurationService');
        $this->current_realm = Config::get('app.url');
        Route::enableFilters();
        Session::start();
    }

    public function testNonePrompt()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array(
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('error', $output));
        $this->assertTrue(!empty($output['error']));
        $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Interaction_Required);

    }

    public function testConsentPrompt()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => IAuthService::AuthorizationResponse_DenyOnce,
            '_token' => Session::token()
        ));

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);
        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('error', $output));
        $this->assertTrue(!empty($output['error']));
        $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Consent_Required);

    }

    public function testConsentLogin()
    {
        //already logged user
        $user = User::where('identifier', '=', 'sebastian.marcet')->first();
        $this->be($user);

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s', OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
                OAuth2Protocol::OAuth2Protocol_Prompt_Login)
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => IAuthService::AuthorizationResponse_DenyOnce,
            '_token' => Session::token()
        ));

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);
        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('error', $output));
        $this->assertTrue(!empty($output['error']));
        $this->assertTrue($output['error'] === OAuth2Protocol::OAuth2Protocol_Error_Consent_Required);

    }

    public function testAuthCode()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

    }

    public function testAuthCodeInvalidLoginHint()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@sebastian.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();


        $url = $response->getTargetUrl();

        $this->assertTrue(str_contains($url, '/login'));

    }

    public function testAuthCodeOpenIdScopeOnly()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'scope' => 'openid',
            'state' => 'KtWzJk5Vmk8CZwC0',
            'redirect_uri' => 'https://op.certification.openid.net:60393/authz_cb',
            'response_type' => 'code',
            'client_id' => $client_id,
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

    }

    public function testMaxAge1AndWait2()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        sleep(2);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $this->assertTrue($response->getTargetUrl() === URL::action("UserController@postLogin"));
    }

    public function testToken
    (
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
        $use_enc = true
    ) {


        $params = array(
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $params = array(
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );


        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));


        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $refresh_token = $response->refresh_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($refresh_token));
        $this->assertTrue(!empty($id_token));

        $jwt = BasicJWTFactory::build($id_token);

        if ($use_enc) {
            $this->assertTrue($jwt instanceof IJWE);

            $recipient_key = RSAJWKFactory::build
            (
                new RSAJWKPEMPrivateKeySpecification
                (
                    TestSeeder::$client_private_key_1,
                    RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                    $jwt->getJOSEHeader()->getAlgorithm()->getString()
                )
            );

            $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key');


            $jwt->setRecipientKey($recipient_key);

            $payload = $jwt->getPlainText();

            $jwt = BasicJWTFactory::build($payload);

            $this->assertTrue($jwt instanceof IJWS);
        }

        return $access_token;
    }

    public function testGetResfreshTokenFromNativeAppNTimes($n=5)
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android.openstack.client';
        $client_secret = '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf';

        $params_auth_code = array
        (
            'client_id'                              => $client_id,
            'redirect_uri'                           => 'androipapp://oidc_endpoint_callback',
            'response_type'                          => 'code',
            'scope'                                  => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope, OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Nonce     => 'test_nonce',
            OAuth2Protocol::OAuth2Protocol_Prompt    => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge    => 3200,
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params_auth_code,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $params = array(
            'code' => $output['code'],
            'redirect_uri' => 'androipapp://oidc_endpoint_callback',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );

        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));


        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $refresh_token = $response->refresh_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($refresh_token));
        $this->assertTrue(!empty($id_token));


        $iteration = 0;
        do {
            $response = $this->action("POST", "OAuth2ProviderController@authorize",
                $params_auth_code,
                array(),
                array(),
                array());


            $this->assertResponseStatus(302);


            $response = $this->action('GET', 'UserController@getConsent');

            $this->assertResponseStatus(200);

            $response = $this->action('POST', 'UserController@getConsent', array(
                'trust' => 'AllowOnce',
                '_token' => Session::token()
            ));

            $this->assertResponseStatus(302);

            // get auth code

            $response = $this->action("GET", "OAuth2ProviderController@authorize",
                array(),
                array(),
                array(),
                array());

            $this->assertResponseStatus(302);

            $url = $response->getTargetUrl();
            $this->assertResponseStatus(302);

            $url = $response->getTargetUrl();

            $comps = @parse_url($url);
            $query = $comps['query'];
            $output = array();
            parse_str($query, $output);

            $this->assertTrue(array_key_exists('code', $output));
            $this->assertTrue(!empty($output['code']));

            $params = array(
                'code'         => $output['code'],
                'redirect_uri' => 'androipapp://oidc_endpoint_callback',
                'grant_type'   => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
            );

            $response = $this->action("POST", "OAuth2ProviderController@token",
                $params,
                array(),
                array(),
                // Symfony interally prefixes headers with "HTTP", so
                array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));


            $this->assertResponseStatus(200);

            $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

            $content = $response->getContent();

            $response = json_decode($content);
            $access_token = $response->access_token;
            $refresh_token = $response->refresh_token;
            $id_token = $response->id_token;

            $this->assertTrue(!empty($access_token));
            $this->assertTrue(!empty($refresh_token));
            $this->assertTrue(!empty($id_token));
            ++$iteration;
        }while( $iteration < $n);
    }

    public function testTokenResponseModePost()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1,
            OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(200);
        $content = $response->getContent();
        $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type'));

        $output = array();
        parse_str($content, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );


        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));

        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $refresh_token = $response->refresh_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($refresh_token));
        $this->assertTrue(!empty($id_token));

        $jwt = BasicJWTFactory::build($id_token);

        $this->assertTrue($jwt instanceof IJWE);

        $recipient_key = RSAJWKFactory::build
        (
            new RSAJWKPEMPrivateKeySpecification
            (
                TestSeeder::$client_private_key_1,
                RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                $jwt->getJOSEHeader()->getAlgorithm()->getString()
            )
        );

        $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key');


        $jwt->setRecipientKey($recipient_key);

        $payload = $jwt->getPlainText();

        $jwt = BasicJWTFactory::build($payload);

        $this->assertTrue($jwt instanceof IJWS);

        $payload = $jwt->getPayload();

        $claims_set = $payload->getClaimSet();
    }

    public function testNativeClientBasicAuth()
    {

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android.openstack.client';
        $client_secret = '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'androipapp://oidc_endpoint_callback',
            'response_type' => 'code',
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 10000,
            OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(200);
        $content = $response->getContent();
        $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type'));

        $output = array();
        parse_str($content, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'androipapp://oidc_endpoint_callback',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );


        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));

        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $refresh_token = $response->refresh_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($refresh_token));
        $this->assertTrue(!empty($id_token));

        $jwt = BasicJWTFactory::build($id_token);

        $this->assertTrue($jwt instanceof UnsecuredJWT);

        $claims_set = $jwt->getClaimSet();

        $this->assertTrue(!is_null($claims_set));

        $params = array(
            'client_id' => $client_id,
            'redirect_uri' => 'androipapp://oidc_endpoint_callback',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_IDTokenHint => $jwt->toCompactSerialization(),
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());
    }

    public function testClientAuthenticationClientSecretJwt()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x2.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $now = time();

        $claim_set = JWTClaimSetFactory::build
        (
            array
            (
                RegisteredJWTClaimNames::Issuer => $client_id,
                RegisteredJWTClaimNames::Subject => $client_id,
                RegisteredJWTClaimNames::Audience => URL::action("OAuth2ProviderController@token"),
                RegisteredJWTClaimNames::JWTID => '123456789',
                RegisteredJWTClaimNames::ExpirationTime => $now + 3600,
                RegisteredJWTClaimNames::IssuedAt => $now
            )
        );

        $key = OctetSequenceJWKFactory::build
        (
            new OctetSequenceJWKSpecification
            (
                $client_secret,
                JSONWebSignatureAndEncryptionAlgorithms::HS512
            )
        );

        $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::HS512);

        $jws = JWSFactory::build
        (
            new JWS_ParamsSpecification
            (
                $key,
                $alg,
                $claim_set
            )
        );

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
            OAuth2Protocol::OAuth2Protocol_ClientAssertionType => ClientAssertionAuthenticationContext::RegisteredAssertionType,
            OAuth2Protocol::OAuth2Protocol_ClientAssertion => $jws->toCompactSerialization()
        );


        $response = $this->action
        (
            "POST",
            "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            array()
        );

        $this->assertResponseStatus(200);
        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $refresh_token = $response->refresh_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($refresh_token));
        $this->assertTrue(!empty($id_token));

        $jwt = BasicJWTFactory::build($id_token);

        $this->assertTrue($jwt instanceof IJWE);

        $recipient_key = RSAJWKFactory::build
        (
            new RSAJWKPEMPrivateKeySpecification
            (
                TestSeeder::$client_private_key_1,
                RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                $jwt->getJOSEHeader()->getAlgorithm()->getString()
            )
        );

        $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key');

        $jwt->setRecipientKey($recipient_key);

        $payload = $jwt->getPlainText();

        $jwt = BasicJWTFactory::build($payload);

        $this->assertTrue($jwt instanceof IJWS);
    }

    public function testClientAuthenticationPrivateKeyJwt()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android2.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'androipapp://oidc_endpoint_callback2',
            'response_type' => 'code',
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $response = $this->action('GET', 'UserController@getConsent');

        $this->assertResponseStatus(200);

        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));

        $now = time();

        $claim_set = JWTClaimSetFactory::build
        (
            array
            (
                RegisteredJWTClaimNames::Issuer => $client_id,
                RegisteredJWTClaimNames::Subject => $client_id,
                RegisteredJWTClaimNames::Audience => URL::action("OAuth2ProviderController@token"),
                RegisteredJWTClaimNames::JWTID => '123456789',
                RegisteredJWTClaimNames::ExpirationTime => $now + 3600,
                RegisteredJWTClaimNames::IssuedAt => $now
            )
        );

        $key = RSAJWKFactory::build
        (
            new RSAJWKPEMPrivateKeySpecification
            (
                TestSeeder::$client_private_key_2,
                RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                JSONWebSignatureAndEncryptionAlgorithms::RS512
            )
        );

        $key->setId('public_key_44');

        $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::RS512);

        $jws = JWSFactory::build
        (
            new JWS_ParamsSpecification
            (
                $key,
                $alg,
                $claim_set
            )
        );

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'androipapp://oidc_endpoint_callback2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
            OAuth2Protocol::OAuth2Protocol_ClientAssertionType => ClientAssertionAuthenticationContext::RegisteredAssertionType,
            OAuth2Protocol::OAuth2Protocol_ClientAssertion => $jws->toCompactSerialization()
        );


        $response = $this->action
        (
            "POST",
            "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            array()
        );

        $this->assertResponseStatus(200);
        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $id_token = $response->id_token;

        $this->assertTrue(!empty($access_token));
        $this->assertTrue(!empty($id_token));
    }

    public function testImplicitFlowTokenIdToken()
    {
        // use a public client

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken . OAuth2Protocol::OAuth2Protocol_ResponseType_Delimiter . OAuth2Protocol::OAuth2Protocol_ResponseType_Token,
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());


        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());


        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get response

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('access_token', $output));
        $this->assertTrue(!empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

    }

    public function testImplicitFlowIdToken()
    {
        // use a public client

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken,
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());


        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );


        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());


        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get response

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(!array_key_exists('access_token', $output));
        $this->assertTrue(empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));
    }

    public function testImplicitFlowIdTokenMaxAge1000()
    {
        // use a public client

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken,
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1000,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());


        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );


        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());


        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get response

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(!array_key_exists('access_token', $output));
        $this->assertTrue(empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

        sleep(10);

        $params[OAuth2Protocol::OAuth2Protocol_Prompt] = OAuth2Protocol::OAuth2Protocol_Prompt_None;
        $params['scope'] = sprintf('%s profile email', OAuth2Protocol::OpenIdConnect_Scope);

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output2 = array();
        parse_str($fragment, $output2);

        $this->assertTrue(!array_key_exists('access_token', $output2));
        $this->assertTrue(empty($output2['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output2));
        $this->assertTrue(!empty($output2['id_token']));

    }

    public function testImplicitFlowAccessToken()
    {
        // use a public client

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_Token,
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());


        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );


        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());


        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get response

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('access_token', $output));
        $this->assertTrue(!empty($output['access_token']));
        $this->assertTrue(!array_key_exists('id_token', $output));
        $this->assertTrue(empty($output['id_token']));

    }

    public function testImplicitFlowResponseModePost()
    {
        // use a public client

        //already given consent

        Session::set("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce);

        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.openstack.client';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken . OAuth2Protocol::OAuth2Protocol_ResponseType_Delimiter . OAuth2Protocol::OAuth2Protocol_ResponseType_Token,
            'scope' => sprintf('%s profile email %s', OAuth2Protocol::OpenIdConnect_Scope,
                OAuth2Protocol::OfflineAccess_Scope),
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_ResponseMode => OAuth2Protocol::OAuth2Protocol_ResponseMode_FormPost,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());


        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        // do login
        $response = $this->call('POST', $url,
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());


        $response = $this->action('POST', 'UserController@getConsent', array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get response

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(200);

        $this->assertEquals('application/x-www-form-urlencoded', $response->headers->get('Content-Type'));
        $content = $response->getContent();
        $this->assertTrue(!empty($content));
        $output = array();
        parse_str($content, $output);

        $this->assertTrue(array_key_exists('access_token', $output));
        $this->assertTrue(!empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

    }

    public function testUserInfoEndpointGETAndBearerHeader()
    {
        $access_token = $this->testToken();
        $response = $this->action("GET", "OAuth2UserApiController@userInfo",
            array(),
            array(),
            array(),
            array("HTTP_Authorization" => " Bearer " . $access_token));

        $this->assertResponseStatus(200);
        $content = $response->getContent();
        $this->assertTrue(!empty($content));
        $user_info = json_decode($content, true);

        $this->assertTrue(isset($user_info['sub']));
    }

    public function testUserInfoEndpointPOSTAndBearerHeader()
    {
        $access_token = $this->testToken();
        $response = $this->action("POST", "OAuth2UserApiController@userInfo",
            array(),
            array(),
            array(),
            array("HTTP_Authorization" => " Bearer " . $access_token));

        $this->assertResponseStatus(200);
        $content = $response->getContent();
        $this->assertTrue(!empty($content));
        $user_info = json_decode($content, true);

        $this->assertTrue(isset($user_info['sub']));
    }

    public function testUserInfoEndpointPOSTAndBearerBody()
    {
        $access_token = $this->testToken();
        $response = $this->action("POST", "OAuth2UserApiController@userInfo",
            array(),
            array
            (
                'access_token' => $access_token
            ),
            array(),
            array());

        $this->assertResponseStatus(200);
        $content = $response->getContent();
        $this->assertTrue(!empty($content));
        $user_info = json_decode($content, true);

        $this->assertTrue(isset($user_info['sub']));
    }

    public function testUserInfoEndpointPOSTAndBearerBodyRS512()
    {
        $access_token = $this->testToken
        (
            'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ33.openstack.client',
            'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N585OtGKhg55',
            false
        );

        $response = $this->action("POST", "OAuth2UserApiController@userInfo",
            array(),
            array
            (
                'access_token' => $access_token
            ),
            array(),
            array());

        $this->assertResponseStatus(200);
        $user_info_response = $response->getContent();

        $this->assertTrue($response->headers->get('content-type') === \utils\http\HttpContentType::JWT);
        $this->assertTrue(!empty($user_info_response));

        $jwt = BasicJWTFactory::build($user_info_response);
        $this->assertTrue($jwt instanceof IJWS);

    }

    public function testHybridFlowCodeIdToken()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'id_token code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1000,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));
        $this->assertTrue(!array_key_exists('access_token', $output));
        $this->assertTrue(empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );

        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));

        $this->assertResponseStatus(200);
    }

    public function testHybridFlowCodeIdTokenIdTokenHint()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'id_token code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1000,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));
        $this->assertTrue(!array_key_exists('access_token', $output));
        $this->assertTrue(empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

        $id_token = $output['id_token'];

        $jwt = BasicJWTFactory::build($id_token);

        $this->assertTrue($jwt instanceof IJWE);

        $recipient_key = RSAJWKFactory::build
        (
            new RSAJWKPEMPrivateKeySpecification
            (
                TestSeeder::$client_private_key_1,
                RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                $jwt->getJOSEHeader()->getAlgorithm()->getString()
            )
        );

        $recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key');
        $jwt->setRecipientKey($recipient_key);
        $payload = $jwt->getPlainText();
        $jwt = BasicJWTFactory::build($payload);
        $this->assertTrue($jwt instanceof IJWS);


        $jwk = OctetSequenceJWKFactory::build
        (
            new OctetSequenceJWKSpecification
            (
                $client_secret,
                JSONWebSignatureAndEncryptionAlgorithms::HS512
            )
        );

        $jwk->setId('shared_secret');

        $jwt->setKey($jwk);

        $verified = $jwt->verify(JSONWebSignatureAndEncryptionAlgorithms::HS512);

        $this->assertTrue($verified);

        // ok send as id token hint again ....

        $id_token_hint = $jwt->toCompactSerialization();

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'id_token code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None,
            OAuth2Protocol::OAuth2Protocol_IDTokenHint => $id_token_hint,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));
        $this->assertTrue(!array_key_exists('access_token', $output));
        $this->assertTrue(empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));

        // try with another key client private key signing (RSA)
        $claim_set = $jwt->getPayload()->getClaimSet();

        $key = RSAJWKFactory::build
        (
            new RSAJWKPEMPrivateKeySpecification
            (
                TestSeeder::$client_private_key_2,
                RSAJWKPEMPrivateKeySpecification::WithoutPassword,
                JSONWebSignatureAndEncryptionAlgorithms::RS512
            )
        );

        $key->setId('public_key_2');

        $alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::RS512);
        $jws = JWSFactory::build( new JWS_ParamsSpecification($key,$alg, $claim_set) );
        // and sign with server private key
        $id_token_hint = $jws->toCompactSerialization();

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'id_token code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_None,
            OAuth2Protocol::OAuth2Protocol_IDTokenHint => $id_token_hint,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('error', $output));
        $this->assertTrue(!empty($output['error']));
        $this->assertTrue($output['error'] == 'server_error');

        $this->assertTrue(array_key_exists('error_description', $output));
        $this->assertTrue(!empty($output['error_description']));
        $this->assertTrue($output['error_description'] == 'original kid public_key_2 - current kid shared_secret');
    }

    public function testHybridFlowCodeAccessToken()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code token',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1000,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));
        $this->assertTrue(array_key_exists('access_token', $output));
        $this->assertTrue(!empty($output['access_token']));
        $this->assertTrue(!array_key_exists('id_token', $output));
        $this->assertTrue(empty($output['id_token']));

        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );

        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));

        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);
        $access_token = $response->access_token;
        $this->assertTrue(!empty($access_token));

        $this->assertTrue($output['access_token'] !== $access_token);

    }

    public function testHybridFlowCodeAccessTokenIdToken()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code token id_token',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 1000,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();
        $comps = @parse_url($url);
        $fragment = $comps['fragment'];

        $this->assertTrue(!empty($fragment));
        $output = array();
        parse_str($fragment, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));
        $this->assertTrue(array_key_exists('access_token', $output));
        $this->assertTrue(!empty($output['access_token']));
        $this->assertTrue(array_key_exists('id_token', $output));
        $this->assertTrue(!empty($output['id_token']));


        $params = array
        (
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );

        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));

        $this->assertResponseStatus(200);
    }

    public function testTryingAuthCodeTwice()
    {
        $client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
        $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';

        $params = array
        (
            'client_id' => $client_id,
            'redirect_uri' => 'https://www.test.com/oauth2',
            'response_type' => 'code',
            'scope' => 'openid profile email',
            OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
            OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
            OAuth2Protocol::OAuth2Protocol_Nonce => 'ctqg5FeNoYnZ',
        );

        $response = $this->action("POST", "OAuth2ProviderController@authorize",
            $params,
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $response = $this->call('GET', $url);

        $this->assertResponseStatus(200);

        // verify that login hint (email) is populated
        $this->assertTrue(str_contains($response->getContent(), 'sebastian@tipit.net'));

        // do login
        $response = $this->action('POST', "UserController@postLogin",
            array
            (
                'username' => 'sebastian@tipit.net',
                'password' => '1qaz2wsx',
                '_token' => Session::token()
            )
        );

        $this->assertResponseStatus(302);

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        //do consent
        $url = $response->getTargetUrl();

        $response = $this->action('POST', "UserController@postConsent", array(
            'trust' => 'AllowOnce',
            '_token' => Session::token()
        ));

        $this->assertResponseStatus(302);

        // get auth code

        $response = $this->action("GET", "OAuth2ProviderController@authorize",
            array(),
            array(),
            array(),
            array());

        $this->assertResponseStatus(302);

        $url = $response->getTargetUrl();

        $comps = @parse_url($url);
        $query = $comps['query'];
        $output = array();
        parse_str($query, $output);

        $this->assertTrue(array_key_exists('code', $output));
        $this->assertTrue(!empty($output['code']));


        $params = array(
            'code' => $output['code'],
            'redirect_uri' => 'https://www.test.com/oauth2',
            'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
        );

        // 1st
        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));


        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);

        // 2nd

        $response = $this->action("POST", "OAuth2ProviderController@token",
            $params,
            array(),
            array(),
            // Symfony interally prefixes headers with "HTTP", so
            array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));


        $this->assertResponseStatus(400);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $response = json_decode($content);

        $this->assertTrue($response->error === 'invalid_grant');
    }

    public function testDiscovery()
    {
        $response = $this->action("GET", "OAuth2ProviderController@discovery");

        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $this->assertTrue(!empty($content));

        $response = json_decode($content, true);

        $this->assertTrue(isset($response['issuer']));
    }

    public function testJWK()
    {
        $response = $this->action("GET", "OAuth2ProviderController@certs");

        $this->assertResponseStatus(200);

        $this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));

        $content = $response->getContent();

        $this->assertTrue(!empty($content));

        $response = json_decode($content, true);

        $this->assertTrue(isset($response['keys']));
    }

}