1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace SilverStripe\MFA\Service; |
||
6 | |||
7 | use SilverStripe\Core\Config\Configurable; |
||
8 | use SilverStripe\Core\Extensible; |
||
9 | use SilverStripe\Core\Injector\Injectable; |
||
10 | use SilverStripe\MFA\State\BackupCode; |
||
11 | use SilverStripe\Security\Security; |
||
12 | |||
13 | class BackupCodeGenerator implements BackupCodeGeneratorInterface |
||
14 | { |
||
15 | use Configurable; |
||
16 | use Extensible; |
||
17 | use Injectable; |
||
18 | |||
19 | /** |
||
20 | * The number of back-up codes that should be generated for a user. Note that changing this value will not |
||
21 | * regenerate or generate new codes to meet the new number. The user will have to manually regenerate codes to |
||
22 | * receive the new number of codes. |
||
23 | * |
||
24 | * @config |
||
25 | * @var int |
||
26 | */ |
||
27 | private static $backup_code_count = 15; |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
28 | |||
29 | /** |
||
30 | * The length of each individual backup code. |
||
31 | * |
||
32 | * @config |
||
33 | * @var int |
||
34 | */ |
||
35 | private static $backup_code_length = 12; |
||
0 ignored issues
–
show
|
|||
36 | |||
37 | /** |
||
38 | * Generates a list of backup codes |
||
39 | * |
||
40 | * {@inheritDoc} |
||
41 | */ |
||
42 | public function generate(): array |
||
43 | { |
||
44 | $codeCount = (int) $this->config()->get('backup_code_count'); |
||
45 | $codeLength = (int) $this->config()->get('backup_code_length'); |
||
46 | $charset = $this->getCharacterSet(); |
||
47 | |||
48 | $codes = []; |
||
49 | while (count($codes) < $codeCount) { |
||
50 | $code = $this->generateCode($charset, $codeLength); |
||
51 | |||
52 | if (!in_array($code, $codes)) { |
||
53 | $hashData = Security::encrypt_password($code); |
||
54 | $codes[] = BackupCode::create($code, $hashData['password'], $hashData['algorithm'], $hashData['salt']); |
||
55 | } |
||
56 | } |
||
57 | |||
58 | return $codes; |
||
59 | } |
||
60 | |||
61 | public function getCharacterSet(): array |
||
62 | { |
||
63 | $characterSet = range('a', 'z'); |
||
64 | |||
65 | $this->extend('updateCharacterSet', $characterSet); |
||
66 | |||
67 | return $characterSet; |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Generates a backup code at the specified string length, using a mixture of digits and mixed case letters |
||
72 | * |
||
73 | * @param array $charset |
||
74 | * @param int $codeLength |
||
75 | * @return string |
||
76 | */ |
||
77 | protected function generateCode(array $charset, int $codeLength): string |
||
78 | { |
||
79 | $characters = []; |
||
80 | $numberOfOptions = count($charset); |
||
81 | while (count($characters) < $codeLength) { |
||
82 | $key = random_int(0, $numberOfOptions - 1); // zero based array |
||
83 | $characters[] = $charset[$key]; |
||
84 | } |
||
85 | return implode($characters); |
||
86 | } |
||
87 | } |
||
88 |