Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 376
0.00% covered (danger)
0.00%
0 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 3
AuthTest
0.00% covered (danger)
0.00%
0 / 307
0.00% covered (danger)
0.00%
0 / 23
552
0.00% covered (danger)
0.00%
0 / 1
 setUpBeforeClass
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 tearDownAfterClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthWithKeyInsecure
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 testNoAuthParams
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthWithToken
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthWithKeyForceToken
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthEmptyForceToken
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenWithoutTLS
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthCallbackReturningSignedRequest
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthCallbackReturningTokenDetails
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthCallbackReturningTokenString
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthUrlReturningSignedRequest
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthUrlReturningTokenDetails
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthUrlReturningTokenString
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 testTokenRequestWithAuthUrlParams
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
2
 testCreateTokenRequestValidity
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 stripTokenRequestVariableParams
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 testCreateTokenRequestParams
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthorize
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthorizeParams
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
2
 testAuthorizeRememberDefaults
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 testHTTPHeadersKey
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 testHTTPHeadersToken
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
HttpMock
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 1
 request
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
42
AuthMock
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 3
20
0.00% covered (danger)
0.00%
0 / 1
 requestToken
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getSavedAuthorizeAuthOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSavedAuthorizeTokenParams
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace authTest;
3use Ably\AblyRest;
4use Ably\Http;
5use Ably\Auth;
6use Ably\Models\TokenDetails;
7use Ably\Models\TokenParams;
8use Ably\Models\TokenRequest;
9use Ably\Exceptions\AblyRequestException;
10
11require_once __DIR__ . '/factories/TestApp.php';
12
13class AuthTest extends \PHPUnit_Framework_TestCase {
14
15    protected static $testApp;
16    protected static $defaultOptions;
17
18    public static function setUpBeforeClass() {
19        self::$testApp = new \tests\TestApp();
20        self::$defaultOptions = self::$testApp->getOptions();
21    }
22
23    public static function tearDownAfterClass() {
24        self::$testApp->release();
25    }
26
27    /**
28     * Init library with a key over unsecure connection
29     */
30    public function testAuthWithKeyInsecure() {
31        $this->setExpectedException( 'Ably\Exceptions\AblyException', '', 40103 );
32
33        $ably = new AblyRest( array_merge( self::$defaultOptions, [
34            'key' => self::$testApp->getAppKeyDefault()->string,
35            'tls' => false,
36        ] ) );
37    }
38
39    /**
40     * Init library without any valid auth
41     */
42    public function testNoAuthParams() {
43        $this->setExpectedException( 'Ably\Exceptions\AblyException', '', 40103 );
44
45        $ably = new AblyRest( );
46    }
47
48    /**
49     * Init library with a token
50     */
51    public function testAuthWithToken() {
52        $ably_for_token = new AblyRest( array_merge( self::$defaultOptions, [
53            'key' => self::$testApp->getAppKeyDefault()->string,
54        ] ) );
55        $tokenDetails = $ably_for_token->auth->requestToken();
56
57        $this->assertNotNull($tokenDetails->token, 'Expected token id' );
58
59        $ably = new AblyRest( array_merge( self::$defaultOptions, [
60            'tokenDetails' => $tokenDetails,
61        ] ) );
62
63        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
64
65        $ably->stats(); // authorized request, should pass
66    }
67
68    /**
69     * Init library with a key, force use of token with useTokenAuth
70     */
71    public function testAuthWithKeyForceToken() {
72        $ably = new AblyRest( [
73            'key' => 'fake.key:totallyFake',
74            'useTokenAuth' => true,
75        ] );
76
77        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
78    }
79
80    /**
81     * Init library without providing a key or a token, force use of token with useTokenAuth
82     */
83    public function testAuthEmptyForceToken() {
84        $this->setExpectedException( 'Ably\Exceptions\AblyException', '', 40103 );
85
86        $ably = new AblyRest( [
87            'useTokenAuth' => true,
88        ] );
89    }
90
91    /**
92     * Verify than token auth works without TLS
93     */
94    public function testTokenWithoutTLS() {
95        $ably_for_token = new AblyRest( array_merge( self::$defaultOptions, [
96            'key' => self::$testApp->getAppKeyDefault()->string,
97        ] ) );
98        $tokenDetails = $ably_for_token->auth->requestToken();
99
100        $this->assertNotNull($tokenDetails->token, 'Expected token id' );
101
102        $ablyInsecure = new AblyRest( array_merge( self::$defaultOptions, [
103            'tokenDetails' => $tokenDetails,
104            'tls' => false,
105        ] ) );
106
107        $this->assertFalse( $ablyInsecure->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
108
109        $ablyInsecure->stats(); // authorized request, should pass
110    }
111
112    /**
113     * Init library with a token callback that returns a signed TokenRequest
114     */
115    public function testTokenRequestWithAuthCallbackReturningSignedRequest() {
116        $callbackCalled = false;
117
118        $ably = new AblyRest( array_merge( self::$defaultOptions, [
119            'authCallback' => function( $tokenParams ) use( &$callbackCalled ) {
120                $callbackCalled = true;
121
122                return new TokenRequest( [
123                    'token' => 'this_is_not_really_a_token_request',
124                    'timestamp' => time()*1000,
125                    'keyName' => 'fakeKeyName',
126                    'mac' => 'not_really_hmac',
127                ] );
128            },
129            'httpClass' => 'authTest\HttpMock',
130        ] ) );
131        
132        $tokenDetails = $ably->auth->requestToken();
133
134        $this->assertTrue( $callbackCalled, 'Expected token callback to be called' );
135        $this->assertEquals( 'mock_token_requestToken', $tokenDetails->token, 'Expected mock token to be used' );
136        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
137    }
138
139    /**
140     * Init library with a token callback that returns TokenDetails
141     */
142    public function testTokenRequestWithAuthCallbackReturningTokenDetails() {
143        $callbackCalled = false;
144
145        $ably = new AblyRest( array_merge( self::$defaultOptions, [
146            'authCallback' => function( $tokenParams ) use( &$callbackCalled ) {
147                $callbackCalled = true;
148
149                return new TokenDetails( [
150                    'token' => 'mock_TokenDetails',
151                    'issued' => time()*1000,
152                    'expires' => time()*1000 + 3600*1000,
153                ] );
154            },
155            'httpClass' => 'authTest\HttpMock',
156        ] ) );
157        
158        $tokenDetails = $ably->auth->requestToken();
159
160        $this->assertTrue( $callbackCalled, 'Expected token callback to be called' );
161        $this->assertEquals( 'mock_TokenDetails', $tokenDetails->token, 'Expected mock token to be used' );
162        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
163    }
164
165    /**
166     * Init library with a token callback that returns token as a string
167     */
168    public function testTokenRequestWithAuthCallbackReturningTokenString() {
169        $callbackCalled = false;
170
171        $ably = new AblyRest( array_merge( self::$defaultOptions, [
172            'authCallback' => function( $tokenParams ) use( &$callbackCalled ) {
173                $callbackCalled = true;
174
175                return 'mock_tokenString';
176            },
177            'httpClass' => 'authTest\HttpMock',
178        ] ) );
179        
180        $tokenDetails = $ably->auth->requestToken();
181
182        $this->assertTrue( $callbackCalled, 'Expected token callback to be called' );
183        $this->assertEquals( 'mock_tokenString', $tokenDetails->token, 'Expected mock token to be used' );
184        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
185    }
186
187    /**
188     * Init library with an authUrl that returns a signed TokenRequest
189     */
190    public function testTokenRequestWithAuthUrlReturningSignedRequest() {
191        $method = 'POST';
192
193        $ably = new AblyRest( array_merge( self::$defaultOptions, [
194            'authUrl' => 'https://TEST/tokenRequest',
195            'httpClass' => 'authTest\HttpMock',
196        ] ) );
197        
198        $tokenDetails = $ably->auth->requestToken();
199        
200        $this->assertEquals( 'mock_token_requestToken', $tokenDetails->token, 'Expected mock token to be used' );
201        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
202    }
203
204    /**
205     * Init library with an authUrl that returns TokenDetails
206     */
207    public function testTokenRequestWithAuthUrlReturningTokenDetails() {
208        $ably = new AblyRest( array_merge( self::$defaultOptions, [
209            'authUrl' => 'https://TEST/tokenDetails',
210            'httpClass' => 'authTest\HttpMock',
211        ] ) );
212        
213        $tokenDetails = $ably->auth->requestToken();
214        
215        $this->assertEquals( 'mock_token_authurl_TokenDetails', $tokenDetails->token, 'Expected mock token to be used' );
216        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
217    }
218
219    /**
220     * Init library with an authUrl that returns a token string
221     */
222    public function testTokenRequestWithAuthUrlReturningTokenString() {
223        $ably = new AblyRest( array_merge( self::$defaultOptions, [
224            'authUrl' => 'https://TEST/tokenString',
225            'httpClass' => 'authTest\HttpMock',
226        ] ) );
227        
228        $tokenDetails = $ably->auth->requestToken();
229        
230        $this->assertEquals( 'mock_token_authurl_tokenString', $tokenDetails->token, 'Expected mock token to be used' );
231        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
232    }
233
234    /**
235     * Verify that auth parameters and their overriding works properly when using authUrl
236     */
237    public function testTokenRequestWithAuthUrlParams() {
238        $headers = [ 'Test header: yes', 'Another one: no' ];
239        $authParams = [ 'param1' => 'value1', 'test' => 1, 'ttl' => 720000 ];
240        $overriddenTokenParams = [ 'ttl' => 360000 ];
241        // authParams and tokenParams should be merged
242        // `ttl` should be overwritten by $overriddenTokenParams
243        $expectedAuthParams = [ 'param1' => 'value1', 'test' => 1, 'ttl' => 360000 ];
244        $method = 'POST';
245
246        $ably = new AblyRest( array_merge( self::$defaultOptions, [
247            'authUrl' => 'https://TEST/tokenRequest',
248            'authHeaders' => $headers,
249            'authParams' => $authParams,
250            'authMethod' => $method,
251            'httpClass' => 'authTest\HttpMock',
252        ] ) );
253        
254        $ably->auth->requestToken( $overriddenTokenParams );
255        
256        $this->assertTrue( is_a( $ably->http, '\authTest\HttpMock' ) , 'Expected HttpMock class to be used' );
257        $this->assertEquals( $headers, $ably->http->headers, 'Expected authHeaders to match' );
258        $this->assertEquals( $expectedAuthParams, $ably->http->params, 'Expected authParams to match' );
259        $this->assertEquals( $method, $ably->http->method, 'Expected authMethod to match' );
260
261        $overriddenAuthParams = [
262            'authHeaders' => [ 'CompletelyNewHeaders: true' ],
263            'authParams' => [ 'completelyNewParams' => 'yes' ],
264        ];
265        $expectedAuthParams = [ 'completelyNewParams' => 'yes', 'ttl' => 360000 ];
266        $forceReauth = true;
267
268        $ably->auth->requestToken( $overriddenTokenParams, $overriddenAuthParams );
269
270        $this->assertEquals( $overriddenAuthParams['authHeaders'], $ably->http->headers, 'Expected authHeaders to be completely replaced' );
271        $this->assertEquals( $expectedAuthParams, $ably->http->params, 'Expected authParams to be completely replaced' );
272    }
273
274    /**
275     * Verify that createTokenRequest() creates valid signed token requests (unique nonce > 16B, valid HMAC)
276     * and checks if ttl can be left blank
277     */
278    public function testCreateTokenRequestValidity() {
279        $ably = new AblyRest( array_merge( self::$defaultOptions, [
280            'key' => self::$testApp->getAppKeyDefault()->string,
281        ] ) );
282
283        $tokenRequest = $ably->auth->createTokenRequest();
284        $tokenRequest2 = $ably->auth->createTokenRequest();
285
286        $this->assertTrue( strlen( $tokenRequest->nonce ) >= 16, 'Expected nonce to be at least 16 bytes long' );
287        $this->assertFalse( $tokenRequest->nonce == $tokenRequest2->nonce, 'Expected nonces to be unique' );
288        $this->assertNotNull( $tokenRequest->mac, 'Expected hmac to be generated' );
289
290        $timestamp = $ably->time();
291
292        $ably = new AblyRest( array_merge( self::$defaultOptions, [
293            'authCallback' => function( $tokenParams ) use( $tokenRequest ) {
294                return $tokenRequest;
295            },
296        ] ) );
297        
298        $ably->auth->authorize();
299
300        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
301        $this->assertGreaterThanOrEqual( $timestamp, $ably->auth->getTokenDetails()->issued,
302            'Expected token issued timestamp to be greater than or equal to the time of the request' );
303
304        $ably->stats(); // requires valid token, throws exception if invalid
305    }
306
307    private function stripTokenRequestVariableParams($tokenRequest) {
308        $arr = $tokenRequest->toArray();
309        unset($arr['nonce']);
310        unset($arr['mac']);
311        unset($arr['timestamp']);
312        return $arr;
313    }
314
315    /**
316     * Verify that createTokenRequest() supports tokenparams, authparams and overrides values correctly
317     */
318    public function testCreateTokenRequestParams() {
319        $ablyKey = new AblyRest( array_merge( self::$defaultOptions, [
320            'key' => self::$testApp->getAppKeyDefault()->string,
321            'httpClass' => 'authTest\HttpMock',
322            'clientId' => 'libClientId',
323            'queryTime' => false,
324            'defaultTokenParams' => new TokenParams( [
325                'ttl' => 1000000,
326                'capability' => '{"test":"dtp"}',
327                'clientId' =>  'dtpClientId',
328            ] ),
329        ] ) );
330
331        $tokenParamsOverride = [
332            'clientId' => 'tokenParamsClientId',
333            'ttl' => 2000000,
334            'capability' => '{"test":"tp"}',
335        ];
336
337        $authOptionsOverride = [
338            'key' => 'testKey.Name:testKeySecret',
339            'clientId' => 'authOptionsClientId',
340            'queryTime' => true,
341        ];
342
343        $tokenReqLib = $ablyKey->auth->createTokenRequest();
344        $this->assertEquals([
345            'ttl' => 1000000,
346            'capability' => '{"test":"dtp"}',
347            'clientId' =>  'libClientId',
348            'keyName' => self::$testApp->getAppKeyDefault()->name,
349        ], $this->stripTokenRequestVariableParams($tokenReqLib), 'Unexpected values in TokenRequest built from ClientOptions');
350        $this->assertNotNull( $tokenReqLib->mac, 'Expected hmac to be generated' );
351        $this->assertFalse( $ablyKey->http->timeQueried, 'Expected server NOT to be queried for time' );
352
353        $tokenReqTokenParams = $ablyKey->auth->createTokenRequest($tokenParamsOverride);
354        $this->assertEquals([
355            'ttl' => 2000000,
356            'capability' => '{"test":"tp"}',
357            'clientId' =>  'tokenParamsClientId',
358            'keyName' => self::$testApp->getAppKeyDefault()->name,
359        ], $this->stripTokenRequestVariableParams($tokenReqTokenParams), 'Unexpected values in TokenRequest built from ClientOptions + TokenParams');
360        $this->assertNotNull( $tokenReqTokenParams->mac, 'Expected hmac to be generated' );
361        $this->assertFalse( $ablyKey->http->timeQueried, 'Expected server NOT to be queried for time' );
362
363        $tokenReqAuthOptions = $ablyKey->auth->createTokenRequest([], $authOptionsOverride);
364        $this->assertEquals([
365            'ttl' => 1000000,
366            'capability' => '{"test":"dtp"}',
367            'clientId' => 'authOptionsClientId',
368            'keyName' => 'testKey.Name',
369        ], $this->stripTokenRequestVariableParams($tokenReqAuthOptions), 'Unexpected values in TokenRequest built from ClientOptions + AuthOptions');
370        $this->assertNotNull( $tokenReqAuthOptions->mac, 'Expected hmac to be generated' );
371        $this->assertTrue( $ablyKey->http->timeQueried, 'Expected server to be queried for time' );
372        $ablyKey->http->timeQueried = false;
373
374        $tokenReqTokenParamsAuthOptions = $ablyKey->auth->createTokenRequest($tokenParamsOverride, $authOptionsOverride);
375        $this->assertEquals(
376            [
377                'ttl' => 2000000,
378                'capability' => '{"test":"tp"}',
379                'clientId' =>  'tokenParamsClientId',
380                'keyName' => 'testKey.Name',
381            ], $this->stripTokenRequestVariableParams($tokenReqTokenParamsAuthOptions),
382            'Unexpected values in TokenRequest built from ClientOptions + TokenParams + AuthOptions'
383        );
384        $this->assertNotNull( $tokenReqTokenParamsAuthOptions->mac, 'Expected hmac to be generated' );
385        $this->assertTrue( $ablyKey->http->timeQueried, 'Expected server to be queried for time' );
386        $ablyKey->http->timeQueried = false;
387    }
388
389    /**
390     * Verify that authorize() switches to token auth, calls requestToken, keeps using the same token, and renews it when forced
391     */
392    public function testAuthorize() {
393        $ably = new AblyRest( array_merge( self::$defaultOptions, [
394            'key' => self::$testApp->getAppKeyDefault()->string,
395            'authClass' => 'authTest\AuthMock'
396        ] ) );
397
398        $this->assertTrue( $ably->auth->isUsingBasicAuth(), 'Expected basic auth to be used' );
399        
400        $this->assertFalse( $ably->auth->requestTokenCalled, 'Expected requestToken not to be called before using authorize()' );
401
402        $tokenOriginal = $ably->auth->authorize();
403
404        $this->assertTrue( $ably->auth->requestTokenCalled, 'Expected authorize() to call requestToken()' );
405
406        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
407        $this->assertInstanceOf( 'Ably\Models\TokenDetails', $tokenOriginal, 'Expected authorize to return a TokenDetails object' );
408
409        $ably->auth->authorize();
410        $this->assertFalse( $tokenOriginal->token == $ably->auth->getTokenDetails()->token, 'Expected token to renew' );
411    }
412
413    /**
414     * Verify that all the parameters are supported and saved as defaults
415     */
416    public function testAuthorizeParams() {
417        $ably = new AblyRest( array_merge( self::$defaultOptions, [
418            'key' => self::$testApp->getAppKeyDefault()->string,
419            'authClass' => 'authTest\AuthMock'
420        ] ) );
421        $ably->auth->fakeRequestToken = true;
422
423        $tokenParams = [
424            'clientId' => 'tokenParamsClientId',
425            'ttl' => 2000000,
426            'capability' => '{"test":"tp"}',
427            'timestamp' => $ably->time(),
428        ];
429
430        $authOptions = [
431            'clientId' => 'authOptionsClientId',
432            'key' => 'testKey.Name:testKeySecret',
433            'token' => 'testToken',
434            'tokenDetails' => new TokenDetails( 'testToken' ),
435            'useTokenAuth' => true,
436            'authCallback' => 'not a callback',
437            'authUrl' =>  'not a url',
438            'authHeaders' => [ 'blah' => 'yes' ],
439            'authParams' => [ 'param' => 'yep' ],
440            'authMethod' => 'TEST',
441            'queryTime' => true,
442        ];
443        
444        // test with empty params first
445        $ably->auth->authorize();
446        $this->assertTrue( $ably->auth->requestTokenCalled, 'Expected authorize() to call requestToken()' );
447        $this->assertEmpty( $ably->auth->lastTokenParams, 'Expected authorize() to pass empty tokenParams to requestToken()');
448        $this->assertEmpty( $ably->auth->lastAuthOptions, 'Expected authorize() to pass empty authOptions to requestToken()');
449        $ably->auth->lastTokenParams = $ably->auth->lastAuthOptions = null;
450
451        // provide both tokenParams and authOptions and see if they get passed to requestToken
452        $ably->auth->authorize( $tokenParams, $authOptions );
453        $this->assertEquals( $tokenParams, $ably->auth->lastTokenParams, 'Expected authorize() to pass provided tokenParams to requestToken()');
454        $this->assertEquals( $authOptions, $ably->auth->lastAuthOptions, 'Expected authorize() to pass provided authOptions to requestToken()');
455        
456        $this->assertFalse ( isset ( $ably->auth->getSavedAuthorizeTokenParams()['timestamp'] ),
457            'Expected authorize() to save provided tokenParams without the `timestamp` field');
458        $this->assertFalse ( isset ( $ably->auth->getSavedAuthorizeAuthOptions()['force'] ),
459            'Expected authorize() to save provided authOptions without the `force` field');
460        $ably->auth->lastTokenParams = $ably->auth->lastAuthOptions = null;
461
462        // provide no tokenParams or authOptions and see if previously saved params get passed to requestToken
463        unset( $tokenParams['timestamp'] ); // expecting timestamp not to be remembered
464
465        $ably->auth->authorize();
466        $this->assertEquals( $tokenParams, $ably->auth->lastTokenParams, 'Expected authorize() to pass saved tokenParams to requestToken()');
467        $this->assertEquals( $authOptions, $ably->auth->lastAuthOptions, 'Expected authorize() to pass saved authOptions to requestToken()');
468        $ably->auth->lastTokenParams = $ably->auth->lastAuthOptions = null;
469
470        // check if parameter overriding works correctly
471        $ably->auth->authorize( [ 'ttl' => 99999 ], [ 'queryTime' => false ] );
472        
473        $expectedTokenParams = $tokenParams; // arrays are copied by value in PHP
474        $expectedTokenParams['ttl'] = 99999;
475        $expectedAuthOptions = $authOptions;
476        $expectedAuthOptions['queryTime'] = false;
477        $this->assertEquals( $expectedTokenParams, $ably->auth->lastTokenParams, 'Expected authorize() to pass combined tokenParams to requestToken()');
478        $this->assertEquals( $expectedAuthOptions, $ably->auth->lastAuthOptions, 'Expected authorize() to pass combined authOptions to requestToken()');
479    }
480
481    /**
482     * Verify that authorize() stores the provided parameters and uses them as defaults from then on
483     */
484    public function testAuthorizeRememberDefaults() {
485        $ably = new AblyRest( array_merge( self::$defaultOptions, [
486            'key' => self::$testApp->getAppKeyDefault()->string,
487            'clientId' => 'originalClientId',
488        ] ) );
489
490        $token1 = $ably->auth->authorize([
491            'ttl' => 10000,
492        ], [
493            'clientId' => 'overriddenClientId',
494        ]);
495
496        $token2 = $ably->auth->authorize();
497
498        $this->assertFalse( $token1 == $token2, 'Expected different tokens to be issued') ;
499        $this->assertEquals( 'overriddenClientId', $ably->auth->clientId, 'Expected to use a new clientId as a default' );
500        $this->assertLessThan( $ably->systemTime() + 20000, $token2->expires, 'Expected to use a new ttl as a default' );
501    }
502
503    /**
504     * When using Basic Auth, the API key is sent in the `Authorization: Basic` header with a Base64 encoded value
505     */
506    public function testHTTPHeadersKey() {
507        $fakeKey = 'fake.key:totallyFake';
508        $ably = new AblyRest( [
509            'key' => $fakeKey,
510            'httpClass' => 'authTest\HttpMock',
511        ] );
512
513        $ably->get("/dummy_test");
514
515        $this->assertRegExp('/Authorization\s*:\s*Basic\s+'.base64_encode($fakeKey).'/i', $ably->http->headers[0]);
516    }
517
518    /**
519     * Verify that the token string is Base64 encoded and used in the `Authorization: Bearer` header
520     */
521    public function testHTTPHeadersToken() {
522        $fakeToken = 'fakeToken';
523        $ably = new AblyRest( [
524            'token' => $fakeToken,
525            'httpClass' => 'authTest\HttpMock',
526        ] );
527
528        $ably->get("/dummy_test");
529
530        $this->assertRegExp('/Authorization\s*:\s*Bearer\s+'.base64_encode($fakeToken).'/i', $ably->http->headers[0]);
531    }
532}
533
534class HttpMock extends Http {
535    public $headers;
536    public $params;
537    public $method;
538    public $timeQueried = false;
539
540    public function request($method, $url, $headers = [], $params = []) {
541        if ( preg_match( '/\/time$/', $url ) ) {
542            $this->timeQueried = true;
543
544            return [
545                'headers' => 'HTTP/1.1 200 OK'."\n",
546                'body' => [ time() ],
547            ];
548        } else if ($url == 'https://TEST/tokenRequest') {
549            $this->method = $method;
550            $this->headers = $headers;
551            $this->params = $params;
552
553            $tokenRequest = new TokenRequest( [
554                'timestamp' => time()*1000,
555                'keyName' => 'fakeKeyName',
556                'mac' => 'not_really_hmac',
557            ] );
558            
559            $response = json_encode( $tokenRequest->toArray() );
560            
561            return [
562                'headers' => 'HTTP/1.1 200 OK'."\n",
563                'body' => json_decode ( $response ),
564            ];
565        } else if ($url == 'https://TEST/tokenDetails') {
566            $this->method = $method;
567            $this->headers = $headers;
568            $this->params = $params;
569            
570            $tokenDetails = new TokenDetails( [
571                'token' => 'mock_token_authurl_TokenDetails',
572                'issued' => time()*1000,
573                'expires' => time()*1000 + 3600*1000,
574            ] );
575            
576            $response = json_encode( $tokenDetails->toArray() );
577            
578            return [
579                'headers' => 'HTTP/1.1 200 OK'."\n",
580                'body' => json_decode ( $response ),
581            ];
582        } else if ($url == 'https://TEST/tokenString') {
583            $this->method = $method;
584            $this->headers = $headers;
585            $this->params = $params;
586            
587            return [
588                'headers' => 'HTTP/1.1 200 OK'."\n",
589                'body' => 'mock_token_authurl_tokenString',
590            ];
591        } else if ( preg_match( '/\/keys\/[^\/]*\/requestToken$/', $url ) ) {
592            // token-generating ably endpoint simulation
593
594            $tokenDetails = new TokenDetails( [
595                'token' => 'mock_token_requestToken',
596                'issued' => time()*1000,
597                'expires' => time()*1000 + 3600*1000,
598            ] );
599            
600            $response = json_encode( $tokenDetails->toArray() );
601            
602            return [
603                'headers' => 'HTTP/1.1 200 OK'."\n",
604                'body' => json_decode ( $response ),
605            ];
606        } else {
607            $this->method = $method;
608            $this->headers = $headers;
609            $this->params = $params;
610
611            return [
612                'headers' => 'HTTP/1.1 200 OK'."\n",
613                'body' => (object) ['defaultRoute' => true],
614            ];
615        }
616    }
617}
618
619class AuthMock extends Auth {
620    public $requestTokenCalled = false;
621    public $fakeRequestToken = false;
622    public $lastTokenParams;
623    public $lastAuthOptions;
624
625    public function requestToken( $tokenParams = [], $authOptions = [] ) {
626        $this->requestTokenCalled = true;
627        $this->lastTokenParams = $tokenParams;
628        $this->lastAuthOptions = $authOptions;
629
630        if ( $this->fakeRequestToken ) return new TokenDetails( 'FAKE' );
631
632        $args = func_get_args();
633        return call_user_func_array( [ 'parent', __FUNCTION__ ], $args ); // passthru
634    }
635
636    public function getSavedAuthorizeAuthOptions() {
637        return $this->defaultAuthorizeAuthOptions;
638    }
639
640    public function getSavedAuthorizeTokenParams() {
641        return $this->defaultAuthorizeTokenParams;
642    }
643
644}