BaseModel::getAttribute()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 10
1
<?php
2
3
namespace IonGhitun\MysqlEncryption\Models;
4
5
use Faker\Factory;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Facades\DB;
10
11
/**
12
 * @method static Builder|self whereEncrypted($column, $value)
13
 * @method static Builder|self whereNotEncrypted($column, $value)
14
 * @method static Builder|self orWhereEncrypted($column, $value)
15
 * @method static Builder|self orWhereNotEncrypted($column, $value)
16
 * @method static Builder|self orderByEncrypted($column, $direction)
17
 */
18
class BaseModel extends Model
19
{
20
    /**
21
     * Elements should be the names of the fields
22
     *
23
     * @var array
24
     */
25
    protected array $encrypted = [];
26
27
    /**
28
     * Elements should be pairs with key column name and value and array with type and optional parameters
29
     *
30
     * @example ['age' => ['randomDigit']]
31
     * @example ['age' => ['numberBetween', '18','50']]
32
     *
33
     * Available types: https://github.com/fzaninotto/Faker
34
     *
35
     * @var array
36
     */
37
    protected array $anonymizable = [];
38
39
    /**
40
     * Get model attribute
41
     *
42
     * @param $key
43
     *
44
     * @return mixed
45
     */
46
    public function getAttribute($key)
47
    {
48
        $value = parent::getAttribute($key);
49
50
        if (array_key_exists($key, $this->relations) || method_exists($this, $key)) {
51
            return $value;
52
        }
53
54
        $value = parent::getAttribute($key);
55
56
        if (in_array($key, $this->encrypted, true)) {
57
            return $this->aesDecrypt($value);
58
        }
59
60
        return $value;
61
    }
62
63
    /**
64
     * Decrypt value
65
     *
66
     * @param $val
67
     * @param string $cypher
68
     * @param bool $mySqlKey
69
     *
70
     * @return false|string
71
     */
72
    public function aesDecrypt($val, string $cypher = 'aes-128-ecb', bool $mySqlKey = true): bool|string
73
    {
74
        $secret = getenv('ENCRYPTION_KEY');
75
76
        $key = $mySqlKey ? $this->generateMysqlAesKey($secret) : $secret;
77
78
        return openssl_decrypt($val, $cypher, $key, 1);
79
    }
80
81
    /**
82
     * Generate encryption key
83
     *
84
     * @param $key
85
     *
86
     * @return string
87
     */
88
    private function generateMysqlAesKey($key): string
89
    {
90
        $generatedKey = str_repeat(chr(0), 16);
91
92
        for ($i = 0, $len = strlen($key); $i < $len; $i++) {
93
            $generatedKey[$i % 16] = $generatedKey[$i % 16] ^ $key[$i];
94
        }
95
96
        return $generatedKey;
97
    }
98
99
    /**
100
     * Set model attribute
101
     *
102
     * @param $key
103
     * @param $value
104
     *
105
     * @return mixed
106
     */
107
    public function setAttribute($key, $value)
108
    {
109
        if (in_array($key, $this->encrypted, true)) {
110
            $value = $this->aesEncrypt($value);
111
        }
112
113
        return parent::setAttribute($key, $value);
114
    }
115
116
    /**
117
     * Encrypt value
118
     *
119
     * @param $val
120
     * @param string $cypher
121
     * @param bool $mySqlKey
122
     *
123
     * @return false|string
124
     */
125
    public function aesEncrypt($val, string $cypher = 'aes-128-ecb', bool $mySqlKey = true): bool|string
126
    {
127
        $secret = getenv('ENCRYPTION_KEY');
128
129
        $key = $mySqlKey ? $this->generateMysqlAesKey($secret) : $secret;
130
131
        return openssl_encrypt($val, $cypher, $key, 1);
132
    }
133
134
    /**
135
     * Get attributes array
136
     *
137
     * @return array
138
     */
139
    public function attributesToArray()
140
    {
141
        $attributes = parent::attributesToArray();
142
143
        foreach ($this->encrypted as $key) {
144
            if (isset($attributes[$key])) {
145
                $attributes[$key] = $this->aesDecrypt($attributes[$key]);
146
            }
147
        }
148
149
        return $attributes;
150
    }
151
152
    /**
153
     * Get original not encrypted
154
     *
155
     * @param $key
156
     * @param $default
157
     *
158
     * @return mixed|array
159
     */
160
    public function getOriginal($key = null, $default = null)
161
    {
162
        if (in_array($key, $this->encrypted, true)) {
163
            return $this->aesDecrypt(Arr::get($this->original, $key, $default));
164
        }
165
166
        return Arr::get($this->original, $key, $default);
167
    }
168
169
    /**
170
     * Get encrypted columns
171
     *
172
     * @return array
173
     */
174
    public function getEncrypted(): array
175
    {
176
        return $this->encrypted;
177
    }
178
179
    /**
180
     * Get anonymizable columns
181
     *
182
     * @return array
183
     */
184
    public function getAnonymizable(): array
185
    {
186
        return $this->anonymizable;
187
    }
188
189
    /**
190
     * Anonymize model fields
191
     *
192
     * @param string|null $locale
193
     *
194
     * @return void
195
     */
196
    public function anonymize(string $locale = null): void
197
    {
198
        $faker = Factory::create($locale ?? (getenv('FAKER_LOCALE') ?? Factory::DEFAULT_LOCALE));
199
200
        foreach ($this->anonymizable as $field => $type) {
201
            if (in_array($field, $this->attributes, true)) {
202
                $method = $type[0];
203
204
                if (count($type) > 1) {
205
                    $this->$field = $faker->$method(array_slice($type, 1));
206
                } else {
207
                    $this->$field = $faker->$method;
208
                }
209
            }
210
        }
211
    }
212
213
    /**
214
     * where for encrypted columns
215
     *
216
     * @param $query
217
     * @param $column
218
     * @param $value
219
     *
220
     * @return mixed
221
     */
222
    public function scopeWhereEncrypted($query, $column, $value): mixed
223
    {
224
        return $query->whereRaw('AES_DECRYPT(' . $column . ', "' . getenv('ENCRYPTION_KEY') . '") LIKE ' . DB::getPdo()->quote($value) . ' COLLATE utf8mb4_general_ci');
225
    }
226
227
    /**
228
     * where not for encrypted columns
229
     *
230
     * @param $query
231
     * @param $column
232
     * @param $value
233
     *
234
     * @return mixed
235
     */
236
    public function scopeWhereNotEncrypted($query, $column, $value): mixed
237
    {
238
        return $query->whereRaw('AES_DECRYPT(' . $column . ', "' . getenv('ENCRYPTION_KEY') . '") NOT LIKE ' . DB::getPdo()->quote($value) . ' COLLATE utf8mb4_general_ci');
239
    }
240
241
    /**
242
     * orWhere for encrypted columns
243
     *
244
     * @param $query
245
     * @param $column
246
     * @param $value
247
     *
248
     * @return mixed
249
     */
250
    public function scopeOrWhereEncrypted($query, $column, $value): mixed
251
    {
252
        return $query->orWhereRaw('AES_DECRYPT(' . $column . ', "' . getenv('ENCRYPTION_KEY') . '") LIKE ' . DB::getPdo()->quote($value) . ' COLLATE utf8mb4_general_ci');
253
    }
254
255
    /**
256
     * orWhere not for encrypted columns
257
     *
258
     * @param $query
259
     * @param $column
260
     * @param $value
261
     *
262
     * @return mixed
263
     */
264
    public function scopeOrWhereNotEncrypted($query, $column, $value): mixed
265
    {
266
        return $query->orWhereRaw('AES_DECRYPT(' . $column . ', "' . getenv('ENCRYPTION_KEY') . '") NOT LIKE ' . DB::getPdo()->quote($value) . ' COLLATE utf8mb4_general_ci');
267
    }
268
269
    /**
270
     * orderBy for encrypted columns
271
     *
272
     * @param $query
273
     * @param $column
274
     * @param $direction
275
     *
276
     * @return mixed
277
     */
278
    public function scopeOrderByEncrypted($query, $column, $direction): mixed
279
    {
280
        return $query->orderByRaw('AES_DECRYPT(' . $column . ', "' . getenv('ENCRYPTION_KEY') . '") ' . $direction);
281
    }
282
}
283