Skip to content

Commit

Permalink
Merge pull request #6 from genkgo/fix_too_many_provider_token_updates
Browse files Browse the repository at this point in the history
fix PreventTooManyProviderTokenUpdates
  • Loading branch information
frederikbosch authored Feb 10, 2021
2 parents 7c2e900 + dab1dfe commit c32c26c
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 67 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/code_checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# .github/workflows/code_checks.yaml
name: code check

on:
pull_request: null
push:
branches:
- master

jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.3', '7.4']

name: PHP ${{ matrix.php }} tests
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v1
with:
php-version: ${{ matrix.php }}
coverage: none

- name: Download dependencies
run: composer update --no-ansi --prefer-stable --prefer-dist --no-interaction --no-progress --no-suggest

- name: Run tests
run: ./vendor/bin/phpunit -c phpunit.xml
- name: Static analysis for source
run: ./vendor/bin/phpstan analyse -l max src
- name: Static analysis for tests
run: ./vendor/bin/phpstan analyse -l max test
- name: Code Style
run: ./vendor/bin/php-cs-fixer fix --dry-run --verbose --config .php_cs.dist ./src ./test
10 changes: 0 additions & 10 deletions .scrutinizer.yml

This file was deleted.

33 changes: 0 additions & 33 deletions .travis.yml

This file was deleted.

13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).


## [3.0.2] - 2021-02-10

### Fixed

- Fix PreventTooManyProviderTokenUpdates


## [3.0.1] - 2020-11-06

### Fixed

- Fix bug kid must header


## [3.0.0] - 2020-05-27

Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ Requires PHP 7.2 or later. It is installable and autoloadable via Composer as [g

### Quality

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/genkgo/push/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/genkgo/push/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/genkgo/push/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/genkgo/push/?branch=master)
[![Build Status](https://travis-ci.org/genkgo/push.png?branch=master)](https://travis-ci.org/genkgo/push)
![workflow code check](https://github.com/genkgo/push/workflows/code%20check/badge.svg)

To run the unit tests at the command line, issue `phpunit -c tests/`. [PHPUnit](http://phpunit.de/manual/) is required.

Expand Down
11 changes: 11 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,16 @@
"psr-4" : {
"Genkgo\\Push\\" : ["test"]
}
},
"scripts": {
"lint": [
"./vendor/bin/php-cs-fixer fix --verbose --config .php_cs.dist ./src ./test"
],
"test": [
"./vendor/bin/phpunit -c phpunit.xml",
"./vendor/bin/php-cs-fixer fix --verbose --dry-run --config .php_cs.dist ./src ./test",
"./vendor/bin/phpstan analyse -l max src",
"./vendor/bin/phpstan analyse -l max -c .phpstan.test.neon test"
]
}
}
79 changes: 58 additions & 21 deletions src/Apn/JwtAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token;

final class JwtAuthenticator implements AuthenticatorInterface
{
Expand All @@ -26,16 +27,70 @@ final class JwtAuthenticator implements AuthenticatorInterface
*/
private $teamId;

/**
* @var string
*/
private $refreshAfter;

/**
* @var \Iterator<int, Token>
*/
private $tokenGenerator;

/**
* @param string $token
* @param string $keyId
* @param string $teamId
* @param string $refreshAfter
*/
public function __construct(string $token, string $keyId, string $teamId)
public function __construct(string $token, string $keyId, string $teamId, string $refreshAfter = 'PT30M')
{
$this->token = $token;
$this->keyId = $keyId;
$this->teamId = $teamId;
$this->refreshAfter = $refreshAfter;
$this->tokenGenerator = $this->newGenerator();
}

/**
* @return \Iterator<int, Token>
*/
private function newGenerator(): \Iterator
{
$now = new \DateTimeImmutable();

$newToken = function () use (&$now) {
$expiration = $now->add(new \DateInterval('PT1H'));

$builder = (new Builder())
->issuedBy($this->teamId)
->issuedAt($now)
->expiresAt($expiration)
->withHeader('kid', $this->keyId);

if (!\file_exists($this->token)) {
throw new \UnexpectedValueException('Cannot find token ' . $this->token . ', invalid path');
}

$keyContent = \file_get_contents($this->token);
if ($keyContent === false) {
throw new \UnexpectedValueException('Cannot fetch token content from ' . $this->token . ', file not readable?');
}

return $builder->getToken(new Sha256(), new Key($keyContent));
};

$lastToken = $newToken();
while (true) {
$newNow = new \DateTimeImmutable();

if ($newNow > $now->add(new \DateInterval($this->refreshAfter))) {
$now = $newNow;
$lastToken = $newToken();
}

yield $lastToken;
}
}

/**
Expand All @@ -47,25 +102,7 @@ public function __construct(string $token, string $keyId, string $teamId)
*/
public function authenticate(Request $request): Request
{
$now = \time();
$expiration = $now + (60 * 60);
$builder = (new Builder())
->issuedBy($this->teamId)
->issuedAt($now)
->expiresAt($expiration)
->withHeader('kid', $this->keyId);

if (!\file_exists($this->token)) {
throw new \UnexpectedValueException('Cannot find token ' . $this->token . ', invalid path');
}

$keyContent = \file_get_contents($this->token);
if ($keyContent === false) {
throw new \UnexpectedValueException('Cannot fetch token content from ' . $this->token . ', file not readable?');
}

$token = $builder->getToken(new Sha256(), new Key($keyContent));

return $request->withHeader('Authorization', \sprintf('Bearer %s', (string)$token));
$this->tokenGenerator->next();
return $request->withHeader('Authorization', \sprintf('Bearer %s', $this->tokenGenerator->current()));
}
}
5 changes: 5 additions & 0 deletions test/Stubs/test.p8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+DTfFvD/nyVqL2Gy
hzWFoG0i1jHnhSSEy8rXKmrYfp2hRANCAARBgZTUCNDF4iB41yMoYExnL7KrVgq9
KnzGjW0FvcpwDsYgYbXGV6e4h1IUtSC4+MubOHqbEZt418DTJM17saPb
-----END PRIVATE KEY-----
46 changes: 46 additions & 0 deletions test/Unit/Apn/JwtAuthenticatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);

namespace Genkgo\Push\Unit\Apn;

use Apple\ApnPush\Protocol\Http\Request;
use Genkgo\Push\AbstractTestCase;
use Genkgo\Push\Apn\JwtAuthenticator;

final class JwtAuthenticatorTest extends AbstractTestCase
{
public function testCreateToken(): void
{
$request = new Request('https://test', 'test');
$authenticator = new JwtAuthenticator(__DIR__ . '/../../Stubs/test.p8', 'AB1234', 'Q12345');
$authRequest = $authenticator->authenticate($request)->getHeaders()['Authorization'];

$this->assertNotEquals('Bearer ', $authRequest);
}

public function testDefaultSameToken(): void
{
$request = new Request('https://test', 'test');
$authenticator = new JwtAuthenticator(__DIR__ . '/../../Stubs/test.p8', 'AB1234', 'Q12345');
$authRequest1 = $authenticator->authenticate($request)->getHeaders()['Authorization'];
\sleep(1);
$authRequest2 = $authenticator->authenticate($request)->getHeaders()['Authorization'];

$this->assertNotEquals('Bearer ', $authRequest1);
$this->assertNotEquals('Bearer ', $authRequest2);
$this->assertEquals($authRequest1, $authRequest2);
}

public function testPreventTooManyProviderTokenUpdates(): void
{
$request = new Request('https://test', 'test');
$authenticator = new JwtAuthenticator(__DIR__ . '/../../Stubs/test.p8', 'AB1234', 'Q12345', 'PT1S');
$authRequest1 = $authenticator->authenticate($request)->getHeaders()['Authorization'];
\sleep(2);
$authRequest2 = $authenticator->authenticate($request)->getHeaders()['Authorization'];

$this->assertNotEquals('Bearer ', $authRequest1);
$this->assertNotEquals('Bearer ', $authRequest2);
$this->assertNotEquals($authRequest1, $authRequest2);
}
}

0 comments on commit c32c26c

Please sign in to comment.