Completed
Branch develop (5d5be1)
by Simon
01:59
created

YubikeyAuthenticator::authenticate()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 20
rs 8.2222
cc 7
eloc 13
nc 4
nop 2
1
<?php
2
namespace Firesphere\YubiAuth;
3
4
use Config;
5
use Controller;
6
use DateTime;
7
use Form;
8
use Injector;
9
use Member;
10
use MemberAuthenticator;
11
use ValidationResult;
12
use Yubikey\Response;
13
14
/**
15
 * Class YubikeyAuthenticator
16
 *
17
 * Enable Yubikey Authentication.
18
 */
19
class YubikeyAuthenticator extends MemberAuthenticator
20
{
21
    /**
22
     * @var null|Form
23
     */
24
    protected static $form = null;
25
26
    /**
27
     * @inheritdoc
28
     *
29
     * @param array $data
30
     * @param Form|null $form
31
     *
32
     * @return null|Member
33
     */
34
    public static function authenticate($data, Form $form = null)
35
    {
36
        self::$form = $form;
37
        Config::inst()->update('Security', 'login_recording', false); // Disable login_recording for this auth.
38
        // First, let's see if we know the member
39
        $member = parent::authenticate($data, $form);
40
        Config::inst()->update('Security', 'login_recording', true); // Enable login_recording again for the rest of the sequence
41
        if ($member && $member instanceof Member) {
42
            // If we know the member, and it's YubiAuth enabled, continue.
43
            if ($member && ($member->YubiAuthEnabled || $data['Yubikey'] !== '')) {
44
                return self::authenticate_yubikey($data, $member);
45
            } elseif (!$member->YubiAuthEnabled) { // We do not have to check the YubiAuth for now.
46
                return self::authenticate_noyubikey($member);
47
            }
48
            $member->registerFailedLogin();
49
        }
50
        self::updateForm();
51
52
        return null;
53
    }
54
55
    /**
56
     * @param Controller $controller
57
     *
58
     * @return Form
59
     */
60
    public static function get_login_form(Controller $controller)
61
    {
62
        return YubikeyLoginForm::create($controller, 'LoginForm');
63
    }
64
65
    public static function get_name()
66
    {
67
        return _t('YubikeyAuthenticator.TITLE', 'Yubikey login');
68
    }
69
70
    /**
71
     * Update the member to forcefully enable YubiAuth
72
     * Also, register the Yubikey to the member.
73
     * Documentation:
74
     * https://developers.yubico.com/yubikey-val/Getting_Started_Writing_Clients.html
75
     *
76
     * @param Member $member
77
     * @param string $yubiString The Identifier String of the Yubikey
78
     */
79
    private static function updateMember($member, $yubiString)
80
    {
81
        if (!$member->YubiAuthEnabled) {
82
            $member->YubiAuthEnabled = true;
83
        }
84
        if (!$member->Yubikey) {
85
            $member->Yubikey = $yubiString;
86
        }
87
        $member->write();
88
    }
89
90
    /**
91
     * @param null|ValidationResult $validation
92
     */
93
    private static function updateForm($validation = null)
94
    {
95
        $form = self::$form;
96
        if ($form) {
97
            if ($validation == null) {
98
                // Default validation error.
99
                $validation = ValidationResult::create(false,
100
                    _t('YubikeyAuthenticator.ERRORYUBIKEY', 'Yubikey authentication error'));
101
            }
102
            $form->sessionMessage($validation->message(), 'bad');
103
        }
104
105
    }
106
107
    /**
108
     * Handle login if the user did not enter a Yubikey string.
109
     * Will break out and return NULL if the member should use their Yubikey
110
     *
111
     * @param Member $member
112
     * @return null|Member
113
     */
114
    private static function authenticate_noyubikey($member)
115
    {
116
        $member->NoYubikeyCount += 1;
117
        $member->write();
118
        $maxNoYubi = self::config()->get('MaxNoYubiLogin');
119 View Code Duplication
        if ($maxNoYubi > 0 && $maxNoYubi <= $member->NoYubikeyCount) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
            $validationError = ValidationResult::create(false,
121
                _t('YubikeyAuthenticator.ERRORMAXYUBIKEY', 'Maximum login without yubikey exceeded'));
122
            self::updateForm($validationError);
123
            $member->registerFailedLogin();
124
125
            return null;
126
        }
127
        $date1 = new DateTime($member->Created);
128
        $date2 = new DateTime(date('Y-m-d'));
129
130
        $diff = $date2->diff($date1)->format("%a");
131
        $maxNoYubiDays = self::config()->get('MaxNoYubiLoginDays');
132
133 View Code Duplication
        if ($maxNoYubiDays > 0 && $diff >= $maxNoYubiDays) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
            $validationError = ValidationResult::create(false,
135
                _t('YubikeyAuthenticator.ERRORMAXYUBIKEYDAYS', 'Maximum days without yubikey exceeded'));
136
            self::updateForm($validationError);
137
            $member->registerFailedLogin();
138
139
            return null;
140
141
        }
142
143
        return $member;
144
    }
145
146
    /**
147
     * Validate a member plus it's yubikey login. It compares the fingerprintt and after that, tries to validate the Yubikey string
148
     * @param array $data
149
     * @param Member $member
150
     * @return null|Member
151
     */
152
    private static function authenticate_yubikey($data, $member)
153
    {
154
        $data['Yubikey'] = strtolower($data['Yubikey']);
155
        $yubiCode = QwertyConvertor::convertString($data['Yubikey']);
156
        $yubiFingerprint = substr($yubiCode, 0, -32);
157
        // If the member has a yubikey ID set, compare it to the fingerprint.
158
        if ($member->Yubikey && strpos($yubiFingerprint, $member->Yubikey) !== 0) {
159
            self::updateForm();
160
161
            return null; // Yubikey id doesn't match the member.
162
        }
163
        $clientID = YUBIAUTH_CLIENTID;
164
        $apiKey = YUBIAUTH_APIKEY;
165
        $service = Injector::inst()->createWithArgs('Yubikey\Validate', array($apiKey, $clientID));
166
        if ($url = self::config()->get('AuthURL')) {
167
            $service->setHost($url);
168
        }
169
        /** @var Response $result */
170
        $result = $service->check($yubiCode);
171
172
        if ($result->success() === true) {
173
            self::updateMember($member, $yubiFingerprint);
174
            if ($member) {
175
                $member->registerSuccessfulLogin();
176
                $member->NoYubikeyCount = 0;
177
                $member->write();
178
            }
179
180
            return $member;
181
        } else {
182
            self::updateForm();
183
184
            return null;
185
        }
186
    }
187
188
}