Completed
Branch master (9a2426)
by Alex
07:13 queued 02:56
created

WebPush::assertConfiguredKeysAreCorrect()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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