TOTPAuthenticator::get_algorithm()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ElliotSawyer\TOTPAuthenticator;
4
5
use Firesphere\BootstrapMFA\Authenticators\BootstrapMFAAuthenticator;
6
use lfkeitel\phptotp\Base32;
7
use lfkeitel\phptotp\Totp;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Core\Config\Configurable;
10
use SilverStripe\ORM\ValidationResult;
11
use SilverStripe\Security\Member;
12
13
/**
14
 * Class TOTPAuthenticator
15
 * @package ElliotSawyer\TOTPAuthenticator
16
 */
17
class TOTPAuthenticator extends BootstrapMFAAuthenticator
18
{
19
    use Configurable;
20
21
    /**
22
     * Google Authenticator and Authy only support tokens generated with SHA-1
23
     * Other authenticators MAY implement SHA-256 or SHA-512 as outlined in RFC6238
24
     * You may use the Config API to adjust this algorithm if you need to support
25
     * a specific TOTP authenticator
26
     */
27
    private static $algorithm = 'sha1';
0 ignored issues
show
introduced by
The private property $algorithm is not used, and could be removed.
Loading history...
28
29
30
    /**
31
     * @param string $link
32
     * @return \SilverStripe\Security\MemberAuthenticator\LoginHandler|static
33
     */
34
    public function getLoginHandler($link)
35
    {
36
        return TOTPLoginHandler::create($link, $this);
37
    }
38
39
    /**
40
     * @param array $data
41
     * @param HTTPRequest $request
42
     * @param ValidationResult $result
43
     * @return bool|null|Member
44
     * @throws \Exception
45
     */
46
    public function validateTOTP($data, $request, &$result)
47
    {
48
        $memberID = $request->getSession()->get(BootstrapMFAAuthenticator::SESSION_KEY . '.MemberID');
49
50
        // First, let's see if we know the member
51
        /** @var Member $member */
52
        $member = Member::get()->byID($memberID);
53
54
        // Continue if we have a valid member
55
        if ($member && $member instanceof Member) {
0 ignored issues
show
introduced by
$member is always a sub-type of SilverStripe\Security\Member.
Loading history...
56
            if (!isset($data['token'])) {
57
                $member->registerFailedLogin();
58
59
                $result->addError(_t(self::class . '.NOTOKEN', 'No token sent'));
60
            } else {
61
                $secret = Base32::decode($member->TOTPSecret);
62
                $key = $this->getTokenFromTOTP($secret);
63
                $user_submitted_key = $data['token'];
64
65
                if ($user_submitted_key !== $key) {
66
                    $result->addError(_t(self::class . '.TOTPFAILED', 'TOTP Failed'));
67
                }
68
            }
69
70
71
            if ($result->isValid()) {
72
                return $member;
73
            }
74
        }
75
76
        $result->addError(_t(self::class . '.NOMEMBER', 'Member not found'));
77
78
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type SilverStripe\ORM\ValidationResult which is incompatible with the documented return type SilverStripe\Security\Member|boolean|null.
Loading history...
79
    }
80
81
    /**
82
     * Given a TOTP secret, use Totp to resolve to a one time token
83
     *
84
     * @param string $secret
85
     * @param string $algorithm If not provided, will default to the configured algorithm
86
     * @return bool|int|string
87
     */
88
    protected function getTokenFromTOTP($secret, $algorithm = '')
89
    {
90
        if (!$algorithm) {
91
            $algorithm = self::get_algorithm();
92
        }
93
94
        $totp = new Totp($algorithm);
95
        return $totp->GenerateToken($secret);
96
    }
97
98
    /**
99
     * Get configured algorithm for TOTP Authenticator
100
     *
101
     * Must be one of: "sha1", "sha256", "sha512"
102
     * If not specified or invalid, default to "sha1"
103
     * @return string
104
     */
105
    public static function get_algorithm()
106
    {
107
        $algorithm = self::config()->get('algorithm');
108
109
        return in_array(strtolower($algorithm), ['sha1', 'sha256', 'sha512'])
110
            ? $algorithm
111
            : 'sha1';
112
    }
113
}
114