1 | <?php |
||
2 | |||
3 | namespace AlexLisenkov\LaravelWebPush; |
||
4 | |||
5 | use AlexLisenkov\LaravelWebPush\Contracts\JWTGeneratorContract; |
||
6 | use AlexLisenkov\LaravelWebPush\Contracts\P256EncryptedMessageBuilderContract; |
||
7 | use AlexLisenkov\LaravelWebPush\Contracts\PushMessageContract; |
||
8 | use AlexLisenkov\LaravelWebPush\Contracts\PushSubscriptionContract; |
||
9 | use AlexLisenkov\LaravelWebPush\Contracts\WebPushContract; |
||
10 | use AlexLisenkov\LaravelWebPush\Exceptions\InvalidPrivateKeyException; |
||
11 | use AlexLisenkov\LaravelWebPush\Exceptions\InvalidPublicKeyException; |
||
12 | use Base64Url\Base64Url; |
||
13 | use GuzzleHttp\Client; |
||
14 | use GuzzleHttp\Promise\PromiseInterface; |
||
15 | use GuzzleHttp\Psr7\Request; |
||
16 | use Illuminate\Contracts\Config\Repository as ConfigRepository; |
||
17 | |||
18 | class WebPush implements WebPushContract |
||
19 | { |
||
20 | /** |
||
21 | * @var ConfigRepository |
||
22 | */ |
||
23 | private $config_repository; |
||
24 | /** |
||
25 | * @var P256EncryptedMessageBuilderContract |
||
26 | */ |
||
27 | private $encrypted_message_builder; |
||
28 | /** |
||
29 | * @var JWTGeneratorContract |
||
30 | */ |
||
31 | private $JWT_generator; |
||
32 | /** |
||
33 | * @var Client |
||
34 | */ |
||
35 | private $client; |
||
36 | |||
37 | /** |
||
38 | * WebPush constructor. |
||
39 | * |
||
40 | * @param ConfigRepository $config_repository |
||
41 | * @param P256EncryptedMessageBuilderContract $encrypted_message_builder |
||
42 | * @param JWTGeneratorContract $JWT_generator |
||
43 | * @param Client $client |
||
44 | */ |
||
45 | 16 | public function __construct( |
|
46 | ConfigRepository $config_repository, |
||
47 | P256EncryptedMessageBuilderContract $encrypted_message_builder, |
||
48 | JWTGeneratorContract $JWT_generator, |
||
49 | Client $client |
||
50 | ) { |
||
51 | 16 | $this->config_repository = $config_repository; |
|
52 | 16 | $this->encrypted_message_builder = $encrypted_message_builder; |
|
53 | 16 | $this->JWT_generator = $JWT_generator; |
|
54 | 16 | $this->client = $client; |
|
55 | 16 | } |
|
56 | |||
57 | 16 | public function sendMessage( |
|
58 | PushMessageContract $message, |
||
59 | PushSubscriptionContract $push_subscription |
||
60 | ): PromiseInterface { |
||
61 | 16 | $private = $this->getConfigVariable('private_key'); |
|
62 | 16 | if (!$this->assertPrivateKeyIsCorrect($private)) { |
|
63 | 1 | throw new InvalidPrivateKeyException('Configured private key is incorrect'); |
|
64 | } |
||
65 | |||
66 | 15 | $public = $this->getConfigVariable('public_key'); |
|
67 | 15 | if (!$this->assertPublicKeyIsCorrect($public)) { |
|
68 | 1 | throw new InvalidPublicKeyException('Configured public key is incorrect'); |
|
69 | } |
||
70 | |||
71 | 14 | if (!$this->assertPublicKeyIsCorrect($push_subscription->getP256dh())) { |
|
72 | throw new InvalidPublicKeyException('Subscriber public key is invalid'); |
||
73 | } |
||
74 | |||
75 | 14 | $encryptedMessage = $this->encrypted_message_builder |
|
76 | 14 | ->withPublicKey($push_subscription->getP256dh()) |
|
77 | 14 | ->withAuthToken($push_subscription->getAuth()) |
|
78 | 14 | ->build($message->toJson()); |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
79 | |||
80 | 14 | $jwt = $this->JWT_generator |
|
81 | 14 | ->withAudience($push_subscription->getAudience()) |
|
82 | 14 | ->serialize(); |
|
83 | |||
84 | $headers = [ |
||
85 | 14 | 'Content-Type' => 'application/octet-stream', |
|
86 | 14 | 'Content-Encoding' => 'aesgcm', |
|
87 | 14 | 'Authorization' => 'WebPush ' . $jwt, |
|
88 | 14 | 'Encryption' => 'salt=' . $encryptedMessage->getEncodedSalt(), |
|
89 | 14 | 'Crypto-Key' => 'dh=' . $encryptedMessage->getEncodedPublicKey() . ';p256ecdsa=' . $this->getConfigVariable('public_key'), |
|
90 | 14 | 'Content-Length' => 'dh=' . $encryptedMessage->getCypherLength(), |
|
91 | 14 | 'TTL' => $this->getConfigVariable('TTL', Constants::DEFAULT_TTL), |
|
92 | ]; |
||
93 | |||
94 | 14 | if ($topic = $message->getTopic()) { |
|
95 | 1 | $headers['Topic'] = $topic; |
|
96 | } |
||
97 | |||
98 | 14 | if ($urgency = $message->getUrgency()) { |
|
99 | 1 | $headers['Urgency'] = $urgency; |
|
100 | } |
||
101 | |||
102 | 14 | $request = new Request('POST', $push_subscription->getEndpoint(), $headers, $encryptedMessage->getCypher()); |
|
103 | |||
104 | 14 | return $this->client->sendAsync($request); |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @param string $key |
||
109 | * |
||
110 | * @param $default |
||
111 | * |
||
112 | * @return mixed |
||
113 | */ |
||
114 | 16 | private function getConfigVariable(string $key, $default = null) |
|
115 | { |
||
116 | 16 | return $this->config_repository->get(Constants::CONFIG_KEY . '.' . $key, $default); |
|
117 | } |
||
118 | |||
119 | /** |
||
120 | * Assert that the given private key is correct by size |
||
121 | * |
||
122 | * @param $private |
||
123 | * |
||
124 | * @return bool |
||
125 | */ |
||
126 | 16 | private function assertPrivateKeyIsCorrect($private): bool |
|
127 | { |
||
128 | try { |
||
129 | 16 | $private_key_decoded = Base64Url::decode($private); |
|
130 | 1 | } catch (\InvalidArgumentException $exception) { |
|
131 | 1 | return false; |
|
132 | } |
||
133 | |||
134 | 15 | return mb_strlen($private_key_decoded, '8bit') === 32; |
|
135 | } |
||
136 | |||
137 | /** |
||
138 | * Assert that the given public key is correct by size |
||
139 | * |
||
140 | * @param $public |
||
141 | * |
||
142 | * @return bool |
||
143 | */ |
||
144 | 15 | private function assertPublicKeyIsCorrect($public): bool |
|
145 | { |
||
146 | try { |
||
147 | 15 | $public_key_decoded = Base64Url::decode($public); |
|
148 | 1 | } catch (\InvalidArgumentException $exception) { |
|
149 | 1 | return false; |
|
150 | } |
||
151 | |||
152 | 14 | return mb_strlen($public_key_decoded, '8bit') === 65; |
|
153 | } |
||
154 | |||
155 | } |
||
156 |