Completed
Push — develop ( 23fb07...aaf888 )
by Fabian
13s queued 11s
created

Decoder   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 42
c 1
b 0
f 0
dl 0
loc 124
rs 10
wmc 12

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A base62Decode() 0 15 3
B decode() 0 52 6
A decrypt() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cakasim\Payone\Sdk\Redirect\Token\Format;
6
7
use Cakasim\Payone\Sdk\Config\ConfigExceptionInterface;
8
use Cakasim\Payone\Sdk\Config\ConfigInterface;
9
use Cakasim\Payone\Sdk\Redirect\Token\Token;
10
use Cakasim\Payone\Sdk\Redirect\Token\TokenInterface;
11
12
/**
13
 * @author Fabian Böttcher <[email protected]>
14
 * @since 0.1.0
15
 */
16
class Decoder implements DecoderInterface
17
{
18
    /**
19
     * @var ConfigInterface The SDK config.
20
     */
21
    protected $config;
22
23
    /**
24
     * @var SignerInterface The token signer.
25
     */
26
    protected $signer;
27
28
    /**
29
     * Constructs the token encoder.
30
     *
31
     * @param ConfigInterface $config The SDK config.
32
     * @param SignerInterface $signer The token signer.
33
     */
34
    public function __construct(ConfigInterface $config, SignerInterface $signer)
35
    {
36
        $this->config = $config;
37
        $this->signer = $signer;
38
    }
39
40
    /**
41
     * @inheritDoc
42
     */
43
    public function decode(string $token): TokenInterface
44
    {
45
        $token = explode('.', $token, 4);
46
47
        if (count($token) !== 3) {
48
            throw new DecoderException("Failed token decoding, the token encoded format is invalid.");
49
        }
50
51
        // Collect parts of the token.
52
        $iv = $token[0];
53
        $signature = $token[2];
54
        $token = $token[1];
55
56
        // base64 decode the token parts.
57
        $iv = $this->base62Decode($iv);
58
        $signature = $this->base62Decode($signature);
59
        $token = $this->base62Decode($token);
60
61
        try {
62
            // Create the expected (valid) signature of the token payload data.
63
            $expectedSignature = $this->signer->createSignature($token);
64
        } catch (SignerExceptionInterface $e) {
65
            throw new DecoderException("Failed token decoding, could not create trusted token signature.", 0, $e);
66
        }
67
68
        // Ensure that signatures match.
69
        if ($signature !== $expectedSignature) {
70
            throw new DecoderException("Failed token decoding, the token signature is invalid.");
71
        }
72
73
        try {
74
            // Load token encryption config.
75
            $encryptionMethod = $this->config->get('redirect.token_encryption_method');
76
            $encryptionKey = $this->config->get('redirect.token_encryption_key');
77
        } catch (ConfigExceptionInterface $e) {
78
            throw new DecoderException("Failed token decoding, the token encryption config is incomplete.", 0, $e);
79
        }
80
81
        // Get binary SHA-256 hash of cleartext encryption key.
82
        $encryptionKey = hash('sha256', $encryptionKey, true);
83
84
        // Decrypt the token payload data.
85
        $token = $this->decrypt($token, $encryptionMethod, $encryptionKey, $iv);
86
87
        // JSON decode the token payload.
88
        $token = json_decode($token, true);
89
90
        if (!is_array($token)) {
91
            throw new DecoderException("Failed token decoding, the token payload data is invalid.");
92
        }
93
94
        return new Token($token);
95
    }
96
97
    /**
98
     * Decodes the provided base64 encoded data.
99
     *
100
     * @param string $data The base64 encoded data to decode.
101
     * @return string The decoded data.
102
     * @throws DecoderExceptionInterface If decoding fails.
103
     */
104
    protected function base62Decode(string $data): string
105
    {
106
        $data = gmp_init($data, 62);
107
108
        if (!($data instanceof \GMP)) {
109
            throw new DecoderException("Failed token decoding, the token could not be base62 decoding.");
110
        }
111
112
        $data = gmp_export($data);
113
114
        if (!is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always true.
Loading history...
115
            throw new DecoderException("Failed token decoding, the token could not be base62 decoding.");
116
        }
117
118
        return $data;
119
    }
120
121
    /**
122
     * Decrypts the provided data.
123
     *
124
     * @param string $data The data to decrypt.
125
     * @param string $method The cipher method.
126
     * @param string $key The encryption key.
127
     * @param string $iv The initialization vector.
128
     * @return string The decrypted data.
129
     * @throws DecoderException If the decryption fails.
130
     */
131
    protected function decrypt(string $data, string $method, string $key, string $iv): string
132
    {
133
        $data = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
134
135
        if (!is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always true.
Loading history...
136
            throw new DecoderException("Failed token decoding, the token could not be decrypted.");
137
        }
138
139
        return $data;
140
    }
141
}
142