Passed
Push — master ( 95a6da...11b724 )
by Russell
12:55
created

VerificationController::getVerificationStatus()   D

Complexity

Conditions 10
Paths 8

Size

Total Lines 46
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 8
nop 1
dl 0
loc 46
rs 4.983
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @author  Russell Michell 2018 <[email protected]>
5
 * @package silverstripe-verifiable
6
 */
7
8
namespace PhpTek\Verifiable\Controller;
9
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\Versioned\Versioned;
13
use PhpTek\Verifiable\ORM\FieldType\ChainpointProof;
14
use SilverStripe\ORM\ValidationException;
15
16
/**
17
 * Accepts incoming requests for data verification e.g. from within the CMS
18
 * or framework's admin area, and sends them on their way.
19
 *
20
 * Will proxy validation requests to the currently configured backend for both
21
 * {@link SiteTree} and {@link DataObject} subclasses.
22
 *
23
 * @todo Take into account LastEdited and Created dates, outside of userland control
24
 * of verifiable_fields
25
 */
26
class VerificationController extends Controller
27
{
28
    /**
29
     * No proof found. Evidence that the record has been tampered-with.
30
     *
31
     * @var string
32
     */
33
    const STATUS_PROOF_NONE = 'No Proof';
34
35
    /**
36
     * Invalid proof found. Evidence that the record has been tampered-with.
37
     *
38
     * @var string
39
     */
40
    const STATUS_PROOF_INVALID = 'Invalid Proof';
41
42
    /**
43
     * Invalid local hash found. Evidence that the record has been tampered-with.
44
     *
45
     * @var string
46
     */
47
    const STATUS_HASH_LOCAL_INVALID = 'Local Hash Invalid';
48
49
    /**
50
     * Invalid remote hash found. Evidence that the record has been tampered-with.
51
     *
52
     * @var string
53
     */
54
    const STATUS_HASH_REMOTE_INVALID_NO_DATA = 'Remote Hash No Data';
55
56
    /**
57
     * Invalid remote hash found. Evidence that the record has been tampered-with.
58
     *
59
     * @var string
60
     */
61
    const STATUS_HASH_REMOTE_INVALID_NO_HASH = 'Remote Hash Not Found';
62
63
    /**
64
     * Invalid UUID. Evidence that the record has been tampered-with.
65
     *
66
     * @var string
67
     */
68
    const STATUS_UUID_INVALID = 'Invalid UUID';
69
70
    /**
71
     * All checks passed. Submitted hash is verified.
72
     *
73
     * @var string
74
     */
75
    const STATUS_VERIFIED = 'Verified';
76
77
    /**
78
     * All checks passed. But submitted hash is not yet verified.
79
     *
80
     * @var string
81
     */
82
    const STATUS_UNVERIFIED = 'Unverified';
83
84
    /**
85
     * All local checks passed. Submitted hash is currently pending.
86
     *
87
     * @var string
88
     */
89
    const STATUS_PENDING = 'Pending';
90
91
    /**
92
     * Some kind of upstream error.
93
     *
94
     * @var string
95
     */
96
    const STATUS_UPSTREAM_ERROR = 'Upstream Error';
97
98
    /**
99
     * @var array
100
     */
101
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
102
        'verifyhash',
103
    ];
104
105
    /**
106
     * Verify the integrity of arbitrary data by means of a single hash.
107
     *
108
     * Responds to URIs of the following prototype: /verifiable/verify/<model>/<ID>/<VID>
109
     * by echoing a JSON response for consumption by client-side logic.
110
     *
111
     * @param  HTTPRequest $request
112
     * @return void
113
     */
114
    public function verifyhash(HTTPRequest $request)
115
    {
116
        $class = $request->param('ClassName');
117
        $id = $request->param('ModelID');
118
        $vid = $request->param('VersionID');
119
120
        if (empty($id) || !is_numeric($id) ||
121
                empty($vid) || !is_numeric($vid) ||
122
                empty($class)) {
123
            return $this->httpError(400, 'Bad request');
124
        }
125
126
        if (!$record = $this->getVersionedRecord($class, $id, $vid)) {
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $id of PhpTek\Verifiable\Contro...r::getVersionedRecord(). ( Ignorable by Annotation )

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

126
        if (!$record = $this->getVersionedRecord($class, /** @scrutinizer ignore-type */ $id, $vid)) {
Loading history...
Bug introduced by
$vid of type string is incompatible with the type integer expected by parameter $version of PhpTek\Verifiable\Contro...r::getVersionedRecord(). ( Ignorable by Annotation )

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

126
        if (!$record = $this->getVersionedRecord($class, $id, /** @scrutinizer ignore-type */ $vid)) {
Loading history...
127
            return $this->httpError(400, 'Bad request');
128
        }
129
130
        try {
131
            $status = $this->getVerificationStatus($record);
132
        } catch (ValidationException $ex) {
133
            $status = self::STATUS_UPSTREAM_ERROR;
134
        }
135
136
        $response = json_encode([
137
            'RecordID' => "$record->RecordID",
0 ignored issues
show
Bug Best Practice introduced by
The property RecordID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
138
            'Version' => "$record->Version",
0 ignored issues
show
Bug Best Practice introduced by
The property Version does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
139
            'Class' => get_class($record),
140
            'Status' => $status,
141
        ], JSON_UNESCAPED_UNICODE);
142
143
        $this->renderJSON($response);
144
    }
145
146
    /**
147
     * Gives us the current verification status of the given record. Takes into
148
     * account the state of the saved proof as well as by making a backend
149
     * verification call.
150
     *
151
     * For the ChainPoint Backend, the following process occurs:
152
     *
153
     * 1. Re-hash verifiable_fields as stored within the "Proof" field
154
     * 2. Assert that the record's "Proof" field is not empty
155
     * 3. Assert that the record's "Proof" field contains a valid proof
156
     * 4. Assert that the new hash exists in the record's "Proof" field
157
     * 5. Assert that hash_node_id for that proof returns a valid response from ChainPoint
158
     * 6. Assert that the returned data contains a matching hash for the new hash
159
     *
160
     * @param  DataObject $record
0 ignored issues
show
Bug introduced by
The type PhpTek\Verifiable\Controller\DataObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
161
     * @return string
162
     * @todo Add tests
163
     */
164
    public function getVerificationStatus($record)
165
    {
166
        // Basic existence of proof (!!) check
167
        if (!$proof = $record->dbObject('Proof')) {
168
            return self::STATUS_PROOF_NONE;
169
        }
170
171
        // Basic proof validity check
172
        // @todo Beef this up to ensure that a basic regex is run over each to ensure it's all
173
        // not just gobbledygook
174
        if (!$proof->getHashIdNode() || !$proof->getHash() || !$proof->getSubmittedAt()) {
175
            return self::STATUS_PROOF_INVALID;
176
        }
177
178
        // Comparison check between locally stored proof, and re-hashed record data
179
        if (!$proof->match($reHash = $this->verifiableService->hash($record->normaliseData()))) {
0 ignored issues
show
Bug introduced by
The method hash() does not exist on null. ( Ignorable by Annotation )

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

179
        if (!$proof->match($reHash = $this->verifiableService->/** @scrutinizer ignore-call */ hash($record->normaliseData()))) {

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...
Bug Best Practice introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Contro...\VerificationController. Since you implemented __get, consider adding a @property annotation.
Loading history...
180
            return self::STATUS_HASH_LOCAL_INVALID;
181
        }
182
183
        // Remote verification check that local hash_node_id returns a valid response
184
        // Responds with a binary format proof
185
        $responseBinary = $this->verifiableService->read($proof->getHashIdNode());
186
187
        if ($responseBinary === '[]') {
188
            return self::STATUS_UUID_INVALID;
189
        }
190
191
        $responseBinaryProof = ChainpointProof::create()->setValue($responseBinary);
192
        $responseVerify = $this->verifiableService->verify($responseBinaryProof->getProof());
193
194
        if ($responseVerify === '[]') {
195
            return self::STATUS_HASH_REMOTE_INVALID_NO_DATA;
196
        }
197
198
        // Compare returned hash matches the re-hash
199
        $responseProof = ChainpointProof::create()->setValue($responseVerify);
200
201
        if (!$responseProof->match($reHash)) {
202
            return self::STATUS_HASH_REMOTE_INVALID_NO_HASH;
203
        }
204
205
        if ($responseProof->getStatus() === 'verified') {
206
            return self::STATUS_VERIFIED;
207
        }
208
209
        return self::STATUS_UNVERIFIED;
210
    }
211
212
    /**
213
     * Fetch a record directly from the relevant table, for the given class
214
     * and ID.
215
     *
216
     * @param  string     $class   A fully-qualified PHP class name.
217
     * @param  int        $id      The "RecordID" of the desired Versioned record.
218
     * @param  int        $version The "Version" of the desired Versioned record.
219
     * @return mixed null | DataObject
220
     */
221
    private function getVersionedRecord(string $class, int $id, int $version)
222
    {
223
        return Versioned::get_version($class, $id, $version);
224
    }
225
226
    /**
227
     * Properly return JSON, allowing consumers to render returned JSON correctly.
228
     *
229
     * @param  string $json
230
     * @return void
231
     */
232
    private function renderJSON(string $json)
233
    {
234
        header('Content-Type: application/json');
235
236
        echo $json;
237
238
        exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
239
    }
240
241
}
242