Passed
Push — master ( ca275c...7ae7c9 )
by Russell
11:47
created

VerificationController::getVerificationStatus()   C

Complexity

Conditions 10
Paths 8

Size

Total Lines 49
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 8
nop 2
dl 0
loc 49
rs 5.5471
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
 * @todo Rename to "VerifiableController"
26
 */
27
class VerificationController extends Controller
28
{
29
    /**
30
     * No proof found. Evidence that the record has been tampered-with.
31
     *
32
     * @var string
33
     */
34
    const STATUS_PROOF_NONE = 'No Proof Found';
35
36
    /**
37
     * Invalid proof found. Evidence that the record has been tampered-with.
38
     *
39
     * @var string
40
     */
41
    const STATUS_PROOF_INVALID = 'Invalid Proof Found';
42
43
    /**
44
     * Invalid local hash found. Evidence that the record has been tampered-with.
45
     *
46
     * @var string
47
     */
48
    const STATUS_HASH_LOCAL_INVALID = 'Local Hash Invalid';
49
50
    /**
51
     * Invalid or no remote proof found. Evidence that the record has been tampered-with.
52
     *
53
     * @var string
54
     */
55
    const STATUS_HASH_REMOTE_INVALID_NO_DATA = 'Remote Hash Not Found';
56
57
    /**
58
     * Invalid remote hash found. Evidence that the record has been tampered-with.
59
     *
60
     * @var string
61
     */
62
    const STATUS_HASH_REMOTE_INVALID_NO_HASH = 'Remote Hash Not Found';
63
64
    /**
65
     * Invalid UUID. Evidence that the record has been tampered-with.
66
     *
67
     * @var string
68
     */
69
    const STATUS_UUID_INVALID = 'Invalid UUID';
70
71
    /**
72
     * All checks passed. Submitted hash is verified.
73
     *
74
     * @var string
75
     */
76
    const STATUS_VERIFIED = 'Verified';
77
78
    /**
79
     * All checks passed. But submitted hash is not yet verified.
80
     *
81
     * @var string
82
     */
83
    const STATUS_UNVERIFIED = 'Unverified';
84
85
    /**
86
     * All local checks passed. Submitted hash is currently pending.
87
     *
88
     * @var string
89
     */
90
    const STATUS_PENDING = 'Pending';
91
92
    /**
93
     * Some kind of upstream error.
94
     *
95
     * @var string
96
     */
97
    const STATUS_UPSTREAM_ERROR = 'Upstream Error';
98
99
    /**
100
     * @var array
101
     */
102
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
103
        'verifyhash',
104
    ];
105
106
    /**
107
     * Verify the integrity of arbitrary data by means of a single hash.
108
     *
109
     * Responds to URIs of the following prototype: /verifiable/verify/<model>/<ID>/<VID>
110
     * by echoing a JSON response for consumption by client-side logic.
111
     *
112
     * @param  HTTPRequest $request
113
     * @return void
114
     */
115
    public function verifyhash(HTTPRequest $request)
116
    {
117
        $class = $request->param('ClassName');
118
        $id = $request->param('ModelID');
119
        $version = $request->param('VersionID');
120
121
        if (empty($id) || !is_numeric($id) ||
122
                empty($version) || !is_numeric($version) ||
123
                empty($class)) {
124
            return $this->httpError(400, 'Bad request');
125
        }
126
127
        if (!$record = Versioned::get_version($class, $id, $version)) {
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $id of SilverStripe\Versioned\Versioned::get_version(). ( Ignorable by Annotation )

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

127
        if (!$record = Versioned::get_version($class, /** @scrutinizer ignore-type */ $id, $version)) {
Loading history...
Bug introduced by
$version of type string is incompatible with the type integer expected by parameter $version of SilverStripe\Versioned\Versioned::get_version(). ( Ignorable by Annotation )

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

127
        if (!$record = Versioned::get_version($class, $id, /** @scrutinizer ignore-type */ $version)) {
Loading history...
128
            return $this->httpError(400, 'Bad request');
129
        }
130
131
        try {
132
            $status = $this->getVerificationStatus($record, $record->getExtraByIndex());
0 ignored issues
show
Bug introduced by
The method getExtraByIndex() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

132
            $status = $this->getVerificationStatus($record, $record->/** @scrutinizer ignore-call */ getExtraByIndex());
Loading history...
133
        } catch (ValidationException $ex) {
134
            $status = self::STATUS_UPSTREAM_ERROR;
135
        }
136
137
        $response = json_encode([
138
            '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...
139
            '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...
140
            'Class' => get_class($record),
141
            'Status' => $status,
142
            'SubmittedAt' => $record->dbObject('Proof')->getSubmittedAt(),
0 ignored issues
show
Bug introduced by
The method getSubmittedAt() does not exist on SilverStripe\ORM\FieldType\DBField. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

142
            'SubmittedAt' => $record->dbObject('Proof')->/** @scrutinizer ignore-call */ getSubmittedAt(),
Loading history...
143
            'SubmittedTo' => $record->dbObject('Extra')->getStoreAsArray(),
0 ignored issues
show
Bug introduced by
The method getStoreAsArray() does not exist on SilverStripe\ORM\FieldType\DBField. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

143
            'SubmittedTo' => $record->dbObject('Extra')->/** @scrutinizer ignore-call */ getStoreAsArray(),
Loading history...
144
        ], JSON_UNESCAPED_UNICODE);
145
146
        $this->renderJSON($response);
147
    }
148
149
    /**
150
     * Gives us the current verification status of the given record. Takes into
151
     * account the state of the saved proof as well as by making a backend
152
     * verification call.
153
     *
154
     * For the ChainPoint Backend, the following process occurs:
155
     *
156
     * 1. Re-hash verifiable_fields as stored within the "Proof" field
157
     * 2. Assert that the record's "Proof" field is not empty
158
     * 3. Assert that the record's "Proof" field contains a valid proof
159
     * 4. Assert that the new hash exists in the record's "Proof" field
160
     * 5. Assert that hash_node_id for that proof returns a valid response from ChainPoint
161
     * 6. Assert that the returned data contains a matching hash for the new hash
162
     *
163
     * @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...
164
     * @param  array      $nodes
165
     * @return string
166
     * @todo Add tests
167
     */
168
    public function getVerificationStatus($record, $nodes)
169
    {
170
        // Set some extra data on the service. In this case, the actual chainpoint
171
        // node addresses, used to submit hashes for the given $record
172
        $this->verifiableService->setNodes($nodes);
0 ignored issues
show
Bug introduced by
The method setNodes() 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

172
        $this->verifiableService->/** @scrutinizer ignore-call */ 
173
                                  setNodes($nodes);

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...
173
174
        // Basic existence of proof (!!) check
175
        if (!$proof = $record->dbObject('Proof')) {
176
            return self::STATUS_PROOF_NONE;
177
        }
178
179
        // Basic proof validity check
180
        // @todo Beef this up to ensure that a basic regex is run over each to ensure it's all
181
        // not just gobbledygook
182
        if (!$proof->getHashIdNode() || !$proof->getHash() || !$proof->getSubmittedAt()) {
183
            return self::STATUS_PROOF_INVALID;
184
        }
185
186
        // Comparison check between locally stored proof, and re-hashed record data
187
        if ($proof->getHash() !== $foo = $this->verifiableService->hash($record->normaliseData())) {
0 ignored issues
show
Unused Code introduced by
The assignment to $foo is dead and can be removed.
Loading history...
188
            return self::STATUS_HASH_LOCAL_INVALID;
189
        }
190
191
        // Remote verification check that local hash_node_id returns a valid response
192
        // Responds with a binary format proof
193
        $responseBinary = $this->verifiableService->read($proof->getHashIdNode());
194
195
        if ($responseBinary === '[]') {
196
            return self::STATUS_UUID_INVALID;
197
        }
198
199
        $responseVerify = $this->verifiableService->verify($responseBinary);
200
201
        if ($responseVerify === '[]') {
202
            return self::STATUS_HASH_REMOTE_INVALID_NO_DATA;
203
        }
204
205
        // Compare returned hash matches the re-hash
206
        $responseProof = ChainpointProof::create()->setValue($responseVerify);
207
208
        if (!$responseProof->match($reHash)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reHash seems to be never defined.
Loading history...
209
            return self::STATUS_HASH_REMOTE_INVALID_NO_HASH;
210
        }
211
212
        if ($responseProof->getStatus() === 'verified') {
213
            return self::STATUS_VERIFIED;
214
        }
215
216
        return self::STATUS_UNVERIFIED;
217
    }
218
219
    /**
220
     * Properly return JSON, allowing consumers to render returned JSON correctly.
221
     *
222
     * @param  string $json
223
     * @return void
224
     */
225
    private function renderJSON(string $json)
226
    {
227
        header('Content-Type: application/json');
228
        echo $json;
229
        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...
230
    }
231
232
}
233