Passed
Push — master ( 9ceb95...784902 )
by Russell
10:38
created

VerificationController::verifyhash()   C

Complexity

Conditions 7
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 3
nop 1
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
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
14
/**
15
 * Accepts incoming requests for data verification e.g. from within the CMS
16
 * or framework's admin area, and sends them on their way.
17
 *
18
 * Will proxy validation requests to the currently configured backend for both
19
 * {@link SiteTree} and {@link DataObject} subclasses.
20
 */
21
class VerificationController extends Controller
22
{
23
    /**
24
     * No proof found. Evidence that the record has been tampered-with.
25
     *
26
     * @var string
27
     */
28
    const STATUS_PROOF_NONE = 'No Proof';
29
30
    /**
31
     * Invalid proof found. Evidence that the record has been tampered-with.
32
     *
33
     * @var string
34
     */
35
    const STATUS_PROOF_INVALID = 'Invalid Proof';
36
37
    /**
38
     * Invalid hash found. Evidence that the record has been tampered-with.
39
     *
40
     * @var string
41
     */
42
    const STATUS_HASH_INVALID = 'Invalid Hash';
43
44
    /**
45
     * Invalid UUID. Evidence that the record has been tampered-with.
46
     *
47
     * @var string
48
     */
49
    const STATUS_UUID_INVALID = 'Invalid UUID';
50
51
    /**
52
     * @var array
53
     */
54
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
55
        'verifyhash',
56
    ];
57
58
    /**
59
     * Verify the integrity of arbitrary data by means of a single hash.
60
     *
61
     * Responds to URIs of the following prototype: /verifiable/verify/<model>/<ID>/<VID>
62
     * by echoing a JSON response for consumption by client-side logic.
63
     *
64
     * @param  HTTPRequest $request
65
     * @return void
66
     */
67
    public function verifyhash(HTTPRequest $request)
68
    {
69
        $class = $request->param('ClassName');
70
        $id = $request->param('ModelID');
71
        $vid = $request->param('VersionID');
72
73
        if (empty($id) || !is_numeric($id) ||
74
                empty($vid) || !is_numeric($vid) ||
75
                empty($class)) {
76
            return $this->httpError(400, 'Bad request');
77
        }
78
79
        if (!$record = $this->getVersionedRecord($class, $id, $vid)) {
0 ignored issues
show
Bug introduced by
$vid of type string is incompatible with the type integer expected by parameter $vid 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

79
        if (!$record = $this->getVersionedRecord($class, $id, /** @scrutinizer ignore-type */ $vid)) {
Loading history...
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

79
        if (!$record = $this->getVersionedRecord($class, /** @scrutinizer ignore-type */ $id, $vid)) {
Loading history...
80
            return $this->httpError(400, 'Bad request');
81
        }
82
83
        $response = json_encode([
84
            '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...
85
            '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...
86
            'Class' => get_class($record),
87
            'Status' => $this->getVerificationStatus($record),
88
        ], JSON_UNESCAPED_UNICODE);
89
90
        $this->renderJSON($response);
91
    }
92
93
    /**
94
     * Gives us the current verification status of the given record. Takes into
95
     * account the state of the saved proof as well as by making a backend
96
     * verification call.
97
     *
98
     * For the ChainPoint Backend, the following process occurs:
99
     *
100
     * 1. Re-hash verifiable_fields as stored within the "Proof" field
101
     * 2. Assert that the record's "Proof" field is not empty
102
     * 3. Assert that the record's "Proof" field contains a valid proof
103
     * 4. Assert that the new hash exists in the record's "Proof" field
104
     * 5. Assert that hash_node_id for that proof returns a valid response from ChainPoint
105
     * 6. Assert that the returned data contains a matching hash for the new hash
106
     *
107
     * @param  StdClass $record
0 ignored issues
show
Bug introduced by
The type PhpTek\Verifiable\Controller\StdClass was not found. Did you mean StdClass? If so, make sure to prefix the type with \.
Loading history...
108
     * @return string
109
     */
110
    public function getVerificationStatus($record)
111
    {
112
        // Basic existence of proof (!!) check
113
        if (!$proof = $record->dbObject('Proof')) {
114
            return self::STATUS_PROOF_NONE;
115
        }
116
117
        // Basic proof validity check
118
        if (!$proof->getHashIdNode() || !$proof->getHash() || !$proof->getSubmittedAt()) {
119
            return self::STATUS_PROOF_INVALID;
120
        }
121
122
        // Comparison check between locally stored proof, and re-hashed record data
123
        if (!$proof->match($reHash = $this->verifiableService->hash($record->normaliseData()))) {
0 ignored issues
show
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...
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

123
        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...
124
            return self::STATUS_HASH_INVALID;
125
        }
126
127
        // Remote verification check that local hash_node_id returns a valid response
128
        $response = $this->verifiableService->read($proof->getHashIdNode());
129
130
        if ($response === '[]') {
131
            return self::STATUS_UUID_INVALID;
132
        }
133
134
        // Compare returned hash matches the re-hash
135
        if (!$proof->match($reHash)) {
136
            return self::STATUS_HASH_INVALID;
137
        }
138
    }
139
140
    /**
141
     * Fetch a record directly from the relevant table, for the given class
142
     * and ID.
143
     *
144
     * @param  string     $class   A fully-qualified PHP class name.
145
     * @param  int        $rid     The "RecordID" of the desired Versioned record.
146
     * @param  int        $version The "Version" of the desired Versioned record.
147
     * @return mixed null | DataObject
148
     * @todo Add an instanceof DataObject check to prevent "SiteTree" being passed for example
149
     */
150
    private function getVersionedRecord(string $class, int $id, int $vid)
151
    {
152
        return Versioned::get_version($class, $id, $vid);
153
    }
154
155
    /**
156
     * Properly return JSON, allowing consumers to render returned JSON correctly.
157
     *
158
     * @param  string $json
159
     * @return void
160
     */
161
    private function renderJSON(string $json)
162
    {
163
        header('Content-Type: application/json');
164
165
        echo $json;
166
167
        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...
168
    }
169
170
}
171