Passed
Push — master ( 2ce6bc...9c94e5 )
by Russell
11:27
created

VerificationController::verifyhash()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 4
nop 1
dl 0
loc 29
rs 5.3846
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
     * Represents a failed verification.
25
     *
26
     * @var string
27
     */
28
    const STATUS_FAILURE = 'FAIL';
29
30
    /**
31
     * Represents a passed verification.
32
     *
33
     * @var string
34
     */
35
    const STATUS_PASSED = 'PASS';
36
37
    /**
38
     * Represents a pending verification.
39
     *
40
     * @var string
41
     */
42
    const STATUS_PENDING = 'PENDING';
43
44
    /**
45
     * @var array
46
     */
47
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
48
        'verifyhash',
49
    ];
50
51
    /**
52
     * Responds to URIs of the following prototype: /verifiable/verify/<model>/<ID>/<VID>
53
     * by echoing a JSON response for consumption by client-side logic.
54
     *
55
     * Also provides x2 extension points, both of which are passed a copy of
56
     * the contents of the managed record's "Proof" {@link ChainpointProof} "Proof"
57
     * field, an indirect {@link DBField} subclass.
58
     *
59
     * - onBeforeVerify()
60
     * - onAfterVerify()
61
     *
62
     * @param  HTTPRequest $request
63
     * @return void
64
     */
65
    public function verifyhash(HTTPRequest $request)
66
    {
67
        $class = $request->param('ClassName');
68
        $id = $request->param('ModelID');
69
        $vid = $request->param('VersionID');
70
71
        if (empty($id) || !is_numeric($id) ||
72
                empty($vid) || !is_numeric($vid) ||
73
                empty($class)) {
74
            return $this->httpError(400, 'Bad request');
75
        }
76
77
        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

77
        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

77
        if (!$record = $this->getVersionedRecord($class, /** @scrutinizer ignore-type */ $id, $vid)) {
Loading history...
78
            return $this->httpError(400, 'Bad request');
79
        }
80
81
        if (!$record->getField('Proof')) {
82
            return $this->httpError(400, 'Bad request');
83
        }
84
85
        $response = json_encode([
86
            '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...
87
            '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...
88
            'Class' => get_class($record),
89
            'Submitted' => $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

89
            'Submitted' => $record->dbObject('Proof')->/** @scrutinizer ignore-call */ getSubmittedAt(),
Loading history...
90
            'Status' => $this->getVerificationStatus($record),
91
        ], JSON_UNESCAPED_UNICODE);
92
93
        $this->renderJSON($response);
94
    }
95
96
    /**
97
     * Gives us the current verification status of the given record. Takes into
98
     * account the state of the saved proof as well as by making a backend
99
     * verification call.
100
     *
101
     * @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...
102
     * @return string
103
     * @todo What _is_ verification? That verifiable_fields data has NOT changed:
104
     *  1. If no full local proof: false
105
     *  2. If sending local proof to /verify fails: false, otherwise;
106
     *  3. Re-calculate hash
107
     *  4. Compare against local "Proof::hash()"
108
     *  5. Send hash-value to /proofs to see if we get a valid proof back
109
     *  6. Is Valid proof? Yes: Verified. No? Tampered-with
110
     */
111
    public function getVerificationStatus($record)
112
    {
113
        $hashToVerify = $this->verifiableService->hash($record->normaliseData()); // <-- Could have been tampered-with
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

113
        /** @scrutinizer ignore-call */ 
114
        $hashToVerify = $this->verifiableService->hash($record->normaliseData()); // <-- Could have been tampered-with

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...
114
        $hashIsVerified = $this->verifiableService->verify($hashToVerify);
115
        $proofData = $record->getField('Proof');
116
        $proofIsPartial = $this->verifiableService->proofIsPartial($proofData);
117
        $proofIsComplete = $this->verifiableService->proofIsComplete($proofData);
118
119
        switch (true) {
120
            case $proofIsComplete && !$hashIsVerified:
121
            default:
122
                return self::STATUS_FAILURE;
123
            case $proofIsPartial && !$hashIsVerified:
124
                return self::STATUS_PENDING;
125
            case $proofIsComplete && $proofIsVerified:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $proofIsVerified seems to be never defined.
Loading history...
126
                return self::STATUS_PASS;
0 ignored issues
show
Bug introduced by
The constant PhpTek\Verifiable\Contro...Controller::STATUS_PASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
127
        }
128
    }
129
130
    /**
131
     * Fetch a record directly from the relevant table, for the given class
132
     * and ID.
133
     *
134
     * @param  string     $class   A fully-qualified PHP class name.
135
     * @param  int        $rid     The "RecordID" of the desired Versioned record.
136
     * @param  int        $version The "Version" of the desired Versioned record.
137
     * @return mixed null | DataObject
138
     * @todo Add an instanceof DataObject check to prevent "SiteTree" being passed for example
139
     */
140
    private function getVersionedRecord(string $class, int $id, int $vid)
141
    {
142
        return Versioned::get_version($class, $id, $vid);
143
    }
144
145
    /**
146
     * Properly return JSON, allowing consumers to render returned JSON correctly.
147
     *
148
     * @param  string $json
149
     * @return void
150
     */
151
    private function renderJSON(string $json)
152
    {
153
        header('Content-Type: application/json');
154
155
        echo $json;
156
157
        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...
158
    }
159
160
}
161