Passed
Push — master ( 1b20af...48b0b4 )
by Charles
02:38
created

JsonRequestParser::checkRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php declare(strict_types=1);
2
3
namespace ncryptf\middleware;
4
5
use Exception;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
10
use Psr\Http\Message\StreamInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
14
use Psr\SimpleCache\CacheInterface;
15
use Psr\SimpleCache\InvalidArgumentException;
16
use ncryptf\Response;
17
18
use ncryptf\Token;
19
use ncryptf\exceptions\DecryptionFailedException;
20
use ncryptf\exceptions\InvalidChecksumException;
21
use ncryptf\exceptions\InvalidSignatureException;
22
23
use ncryptf\middleware\EncryptionKeyInterface;
24
25
final class JsonRequestParser implements MiddlewareInterface
26
{
27
    /**
28
     * @var CacheInterface $cache
29
     */
30
    protected $cache;
31
32
    /**
33
     * @var array $contentType
34
     */
35
    protected $contentType = [
36
        'application/vnd.25519+json',
37
        'application/vnd.ncryptf+json'
38
    ];
39
40
    /**
41
     * Constructor
42
     * @param CacheInterface $cache     A PSR-16 CacheInterface
43
     */
44
    public function __construct(CacheInterface $cache)
45
    {
46
        $this->cache = $cache;
47
    }
48
49
    /**
50
     * Processes the request
51
     * @param ServerRequestInterface $request
52
     * @param RequestHandlerInterface $handler
53
     */
54
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
55
    {
56
        if ($this->checkRequest($request)) {
57
            try {
58
                $rawBody = \base64_decode($request->getBody()->getContents());
59
                if ($rawBody === '') {
60
                    $request = $request->withParsedBody([])
61
                        ->withAttribute('ncryptf-decrypted-body', '')
62
                        ->withAttribute('ncryptf-version', 2)
63
                        ->withAttribute('ncryptf-request-public-key', \base64_decode($request->getHeaderLine('x-pubkey')));
64
                } else {
65
                    $version = Response::getVersion($rawBody);
66
                    $key = $this->getEncryptionKey($request);
67
68
                    $body = $this->decryptRequest($key, $request, $rawBody, $version);
69
70
                    $request = $request->withParsedBody(\json_decode($body, true))
71
                        ->withAttribute('ncryptf-decrypted-body', $body)
72
                        ->withAttribute('ncryptf-version', $version)
73
                        ->withAttribute('ncryptf-request-public-key', $version === 2 ? Response::getPublicKeyFromResponse($rawBody) : \base64_decode($request->getHeaderLine('x-pubkey')));
74
                }
75
            } catch (DecryptionFailedException | InvalidArgumentException | InvalidSignatureException | InvalidChecksumException | Exception $e) {
76
                return $this->createResponse(400);
0 ignored issues
show
Bug introduced by
The method createResponse() does not exist on ncryptf\middleware\JsonRequestParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

76
                return $this->/** @scrutinizer ignore-call */ createResponse(400);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
77
            }
78
        }
79
80
        // Only attempt to process this request if it is a vnd.25519+json or vnd.ncryptf+json request
81
        // Otherwise, continue with normal processing
82
        return $handler->handle($request);
83
    }
84
85
    /**
86
     * Decrypts a request
87
     * @param EncryptionKeyInterface $key
88
     * @param ServerRequestInterface $request
89
     * @param string $rawBody
90
     * @param int $version
91
     * @return string|null
92
     */
93
    private function decryptRequest(EncryptionKeyInterface $key, ServerRequestInterface $request, string $rawBody, int $version) :? string
94
    {
95
        static $response = null;
96
        static $nonce = null;
97
        static $publicKey = null;
98
99
        $response = new Response(
100
            $key->getBoxSecretKey()
101
        );
102
103
        if ($version === 1) {
104
            if (!$request->hasHeader('x-pubkey') || !$request->hasHeader('x-nonce')) {
105
                throw new Exception('Missing nonce or public key header. Unable to decrypt response.');
106
            }
107
108
            $publicKey = \base64_decode($request->getHeaderLine('x-pubkey'));
109
            $nonce = \base64_decode($request->getHeaderLine('x-nonce'));
110
        }
111
112
        $decryptedRequest = $response->decrypt(
113
            $rawBody,
114
            $publicKey,
115
            $nonce
116
        );
117
118
        $hashKey = $request->getHeaderLine('x-hashid');
119
        if ($key->isEphemeral()) {
120
            $this->cache->delete($hashKey);
121
        }
122
123
        return $decryptedRequest;
124
    }
125
126
    /**
127
     * Determines the key from the X-HashId header
128
     * @param ServerRequestInterface
129
     * @return EncryptionKeyInterface
130
     */
131
    private function getEncryptionKey(ServerRequestInterface $request) : EncryptionKeyInterface
132
    {
133
        if (!$request->hasHeader('x-hashid')) {
134
            throw new Exception('Unable to decrypt request.');
135
        }
136
137
        $hashKey = $request->getHeaderLine('x-hashid');
138
139
        try {
140
            $result = $this->cache->get($hashKey);
141
142
            if (!$result) {
143
                throw new Exception('Unable to extract key from cache.');
144
            }
145
146
            if (\function_exists('igbinary_unserialize')) {
147
                return \igbinary_unserialize($result);
148
            }
149
150
            return \unserialize($result);
151
        } catch (InvalidArgumentException $e) {
152
            throw new Exception('Unable to decrypt request.', null, $e);
153
        }
154
    }
155
156
    /**
157
     * Check whether the request payload need to be processed
158
     * @param ServerRequestInterface $request
159
     * @return bool
160
     */
161
    private function checkRequest(ServerRequestInterface $request): bool
162
    {
163
        $contentType = $request->getHeaderLine('Content-Type');
164
        foreach ($this->contentType as $allowedType) {
165
            if (\stripos($contentType, $allowedType) === 0) {
166
                return true;
167
            }
168
        }
169
        return false;
170
    }
171
}
172