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

VerifiableExtension::proofExists()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 4
nop 1
dl 0
loc 7
rs 9.4285
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\Verify;
9
10
use SilverStripe\ORM\DataExtension;
11
use PhpTek\Verifiable\ORM\Fieldtype\ChainpointProof;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\LiteralField;
14
15
/**
16
 * By attaching this extension to any {@link DataObject} subclass and declaring a
17
 * $verifiable_fields array in YML config, all subsequent database writes will
18
 * be passed through here via {@link $this->onAfterWrite()};
19
 *
20
 * This {@link DataExtension} also provides a single field to which all verified
21
 * and verifiable chainpoint proofs are stored in a queryable JSON-aware field.
22
 *
23
 * @todo Store the hash function used and if subsequent verifications
24
 *       fail because differing hash functions are used, throw an exception.
25
 * @todo Hard-code "Created" and "LastEdited" fields into "verifiable_fields"
26
 * @todo Prevent "Proof" field from ever being configured in verifiable_fields
27
 *
28
 */
29
class VerifiableExtension extends DataExtension
30
{
31
    /**
32
     * Declares a JSON-aware {@link DBField} where all chainpoint proofs are stored.
33
     *
34
     * @var array
35
     * @config
36
     */
37
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
38
        'Proof' => ChainpointProof::class,
39
    ];
40
41
    /**
42
     * The field-values that will be hashed and committed to the current backend.
43
     *
44
     * @var array
45
     * @config
46
     */
47
    private static $verifiable_fields = [];
0 ignored issues
show
introduced by
The private property $verifiable_fields is not used, and could be removed.
Loading history...
48
49
    /**
50
     * Write -->onAfterWrite() recursion prevention.
51
     *
52
     * @var string
53
     */
54
    protected static $has_write_occured = false;
55
56
    /**
57
     * After each write, data from the $verifiable_fields config is compiled
58
     * into a string, hashed and submitted to the current backend.
59
     *
60
     * Once written, we periodically poll the backend to receive the full
61
     * chainpoint proof (it takes time for Bitcoin's PoW confirmations, not so
62
     * much for Ethereum).
63
     *
64
     * We need this "complete" proof for subsequent verification checks
65
     * also made against the same backend in the future.
66
     *
67
     * If only the "Proof" field has been written-to, or no-data is found in the
68
     * verifiable_fields, this should not constitute a write that we need to do
69
     * anything with, and it's therefore skipped.
70
     *
71
     * @return void
72
     * @todo Add a check into $skipWriteProof for checking _only_ "Proof" having changed.
73
     * This should _not_ constitute a change requiting a call to the backend for.
74
     */
75
    public function onAfterWrite()
76
    {
77
        parent::onAfterWrite();
78
79
        // Overflow protection should bad recursion occur
80
        static $write_count = 0;
81
        $write_count++;
82
83
        $verifiable = $this->normaliseData();
84
        $skipWriteProof = self::$has_write_occured === true || !count($verifiable) || $write_count > 1;
85
86
        if ($skipWriteProof) {
87
            // Now we fire-off a job to check if verification is complete
88
            // TODO: Use AsyncPHP and use a callback OR check if API has a callback
89
            // endpoint it can call on our end (Tierion's API does)
90
            // $this->verifiableService->queueVerification($this->getOwner());
91
92
            return;
93
        }
94
95
        if ($proofData = $this->verifiableService->write($verifiable)) {
0 ignored issues
show
Bug introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Verify\VerifiableExtension. Did you mean verifiable_fields?
Loading history...
96
            self::$has_write_occured = true;
0 ignored issues
show
Documentation Bug introduced by
The property $has_write_occured was declared of type string, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
97
98
            $this->writeProof($proofData);
99
        }
100
    }
101
102
    /**
103
     * Normalise this model's data so it's suited to being hashed.
104
     *
105
     * @return array
106
     * @todo use array_reduce()?
107
     */
108
    public function normaliseData() : array
109
    {
110
        $fields = $this->getOwner()->config()->get('verifiable_fields');
111
        $verifiable = [];
112
113
        foreach ($fields as $field) {
114
            $verifiable[] = (string) $this->getOwner()->getField($field);
115
        }
116
117
        return $verifiable;
118
    }
119
120
    /**
121
     * Passed an array of fields and their values, this method will hash them
122
     * and check that a chainpoint proof exists in the local database. If unsuccessful
123
     * we return false. Otherwise, we return true.
124
     *
125
     * If a matching proof is found both locally then the supplied
126
     * data is said to have been verified at least once
127
     *
128
     * @param  array $data An array of data to verify against the current backend.
129
     * @return mixed null | ChainpointProof
130
     */
131
    public function proofExists(array $data)
132
    {
133
        $hash = $this->verifiableService->hash($data);
0 ignored issues
show
Bug introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Verify\VerifiableExtension. Did you mean verifiable_fields?
Loading history...
134
        $proof = $this->getOwner()->dbObject('Proof');
135
136
        // Does the passed hash-of-the-data match the hash in the Proof?
137
        return $proof->exists() && $proof->getHash() === $hash ? $proof : null;
138
    }
139
140
    /**
141
     * Writes string data that is assumed to be JSON (as returned from a
142
     * web-service for example) and saved to this decorated object's "Proof"
143
     * field.
144
     *
145
     * @param  mixed $proof
146
     * @return void
147
     */
148
    public function writeProof($proof)
149
    {
150
        if (is_array($proof)) {
151
            $proof = json_encode($proof);
152
        }
153
154
        $owner = $this->getOwner();
155
        $owner->setField('Proof', $proof);
156
        $owner->write();
157
    }
158
159
    /**
160
     * @param  FieldList $fields
161
     * @return void
162
     */
163
    public function updateCMSFields(FieldList $fields)
164
    {
165
        parent::updateCMSFields($fields);
166
167
        $class = get_class($this->getOwner());
168
        $id = $this->getOwner()->ID;
169
        $content = sprintf('<p class="verification-field"><a href="/verify/%s/%d">Verifiy</a>', $class, $id);
170
171
        $fields->insertBefore('Title', LiteralField::create('verify', $content));
172
    }
173
174
}
175