Passed
Push — master ( f57ca7...0b60a8 )
by Garion
02:37 queued 10s
created

BackupCodeGenerator::hash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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