-
Notifications
You must be signed in to change notification settings - Fork 640
/
Security.php
221 lines (196 loc) · 7.15 KB
/
Security.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/
namespace craft\services;
use Craft;
use craft\helpers\FileHelper;
use yii\base\Exception;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\helpers\Inflector;
/**
* Security service.
*
* An instance of the service is available via [[\yii\base\Application::getSecurity()|`Craft::$app->security`]].
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.0.0
*/
class Security extends \yii\base\Security
{
/**
* @var string[] Keywords used to reference sensitive data
* @see redactIfSensitive()
*/
public array $sensitiveKeywords = [];
/**
* @var mixed
*/
private mixed $_blowFishHashCost = null;
/**
* @inheritdoc
*/
public function init(): void
{
parent::init();
$this->_blowFishHashCost = Craft::$app->getConfig()->getGeneral()->blowfishHashCost;
// normalize the sensitive keywords
$this->sensitiveKeywords = array_map(
fn(string $word) => Inflector::camel2words($word, false),
$this->sensitiveKeywords,
);
}
/**
* @return int
*/
public function getMinimumPasswordLength(): int
{
return 6;
}
/**
* Hashes a given password with the bcrypt blowfish encryption algorithm.
*
* @param string $password The string to hash
* @param bool $validateHash If you want to validate the just generated hash. Will throw an exception if
* validation fails.
* @return string The hash.
*/
public function hashPassword(string $password, bool $validateHash = false): string
{
$hash = $this->generatePasswordHash($password, $this->_blowFishHashCost);
if ($validateHash && !$this->validatePassword($password, $hash)) {
throw new InvalidArgumentException('Could not hash the given string.');
}
return $hash;
}
/**
* @inheritdoc
* @param string $data the data to be protected
* @param string|null $key the secret key to be used for generating hash. Should be a secure
* cryptographic key.
* @param bool $rawHash whether the generated hash value is in raw binary format. If false, lowercase
* hex digits will be generated.
* @return string the data prefixed with the keyed hash
* @throws Exception if the validation key could not be written
* @throws InvalidConfigException when HMAC generation fails.
* @see validateData()
* @see generateRandomKey()
* @see hkdf()
* @see pbkdf2()
*/
public function hashData($data, $key = null, $rawHash = false): string
{
if ($key === null) {
$key = Craft::$app->getConfig()->getGeneral()->securityKey;
}
return parent::hashData($data, $key, $rawHash);
}
/**
* @inheritdoc
* @param string $data the data to be validated. The data must be previously
* generated by [[hashData()]].
* @param string|null $key the secret key that was previously used to generate the hash for the data in [[hashData()]].
* function to see the supported hashing algorithms on your system. This must be the same
* as the value passed to [[hashData()]] when generating the hash for the data.
* @param bool $rawHash this should take the same value as when you generate the data using [[hashData()]].
* It indicates whether the hash value in the data is in binary format. If false, it means the hash value consists
* of lowercase hex digits only.
* hex digits will be generated.
* @return string|false the real data with the hash stripped off. False if the data is tampered.
* @throws Exception if the validation key could not be written
* @throws InvalidConfigException when HMAC generation fails.
* @see hashData()
*/
public function validateData($data, $key = null, $rawHash = false): string|false
{
if ($key === null) {
$key = Craft::$app->getConfig()->getGeneral()->securityKey;
}
return parent::validateData($data, $key, $rawHash);
}
/**
* @inheritdoc
* @param string $data the data to encrypt
* @param string|null $inputKey the input to use for encryption and authentication
* @param string $info optional context and application specific information, see [[hkdf()]]
* @return string the encrypted data
* @throws InvalidConfigException on OpenSSL not loaded
* @throws Exception on OpenSSL error
* @see decryptByKey()
* @see encryptByPassword()
*/
public function encryptByKey($data, $inputKey = null, $info = null): string
{
if ($inputKey === null) {
$inputKey = Craft::$app->getConfig()->getGeneral()->securityKey;
}
return parent::encryptByKey($data, $inputKey, $info);
}
/**
* @inheritdoc
* @param string $data the encrypted data to decrypt
* @param string|null $inputKey the input to use for encryption and authentication
* @param string $info optional context and application specific information, see [[hkdf()]]
* @return string|false the decrypted data or false on authentication failure
* @throws InvalidConfigException on OpenSSL not loaded
* @throws Exception on OpenSSL error
* @see encryptByKey()
*/
public function decryptByKey($data, $inputKey = null, $info = null): string|false
{
if ($inputKey === null) {
$inputKey = Craft::$app->getConfig()->getGeneral()->securityKey;
}
return parent::decryptByKey($data, $inputKey, $info);
}
/**
* Returns whether the given key appears to be sensitive.
*
* @param string $key
* @return bool
* @since 3.7.24
*/
public function isSensitive(string $key): bool
{
return preg_match('/\b(' . implode('|', $this->sensitiveKeywords) . ')\b/', Inflector::camel2words($key, false));
}
/**
* Checks the given key to see if it looks like it contains sensitive info, and if so, redacts the given value.
*
* @param string $key
* @param mixed $value
* @return mixed The possibly-redacted value
*/
public function redactIfSensitive(string $key, mixed $value): mixed
{
if (is_array($value)) {
foreach ($value as $n => &$v) {
$v = $this->redactIfSensitive($n, $v);
}
} elseif (is_string($value) && $this->isSensitive($key)) {
$value = str_repeat('•', strlen($value));
}
return $value;
}
/**
* Returns whether the given file path is located within or above any system directories.
*
* @param string $path
* @return bool
* @since 5.4.2
*/
public function isSystemDir(string $path): bool
{
$path = FileHelper::absolutePath($path, '/');
foreach (Craft::$app->getPath()->getSystemPaths() as $dir) {
$dir = FileHelper::absolutePath($dir, '/');
if (str_starts_with("$path/", "$dir/") || str_starts_with("$dir/", "$path/")) {
return true;
}
}
return false;
}
}