Completed
Push — master ( aeeaec...7040ab )
by Simon
01:39
created

YubikeyAuthenticator::authenticate_noyubikey()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 31
Code Lines 21

Duplication

Lines 17
Ratio 54.84 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 17
loc 31
rs 8.439
cc 5
eloc 21
nc 3
nop 1
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
13
/**
14
 * Class YubikeyAuthenticator
15
 *
16
 * Enable Yubikey Authentication.
17
 */
18
class YubikeyAuthenticator extends MemberAuthenticator
19
{
20
    /**
21
     * @var null|Form
22
     */
23
    protected static $form = null;
24
25
    /**
26
     * @inheritdoc
27
     *
28
     * @param array $data
29
     * @param Form|null $form
30
     *
31
     * @return null|Member
32
     */
33
    public static function authenticate($data, Form $form = null)
34
    {
35
        self::$form = $form;
36
        Config::inst()->update('Security', 'login_recording', false); // Disable login_recording for this auth.
37
        // First, let's see if we know the member
38
        $member = parent::authenticate($data, $form);
39
        Config::inst()->update('Security', 'login_recording', true); // Enable login_recording again for the rest of the sequence
40
        if ($member && $member instanceof Member) {
41
            // If we know the member, and it's YubiAuth enabled, continue.
42
            if ($member && ($member->YubiAuthEnabled || $data['Yubikey'] !== '')) {
43
                return self::authenticate_yubikey($data, $member);
44
            } elseif (!$member->YubiAuthEnabled) { // We do not have to check the YubiAuth for now.
45
                return self::authenticate_noyubikey($member);
46
            }
47
        }
48
        if ($member) {
49
            $member->registerFailedLogin();
50
        }
51
        self::updateForm();
52
53
        return null;
54
    }
55
56
    /**
57
     * @param Controller $controller
58
     *
59
     * @return Form
60
     */
61
    public static function get_login_form(Controller $controller)
62
    {
63
        return YubikeyLoginForm::create($controller, 'LoginForm');
64
    }
65
66
    public static function get_name()
67
    {
68
        return _t('YubikeyAuthenticator.TITLE', 'Yubikey login');
69
    }
70
71
    /**
72
     * Update the member to forcefully enable YubiAuth
73
     * Also, register the Yubikey to the member.
74
     * Documentation:
75
     * https://developers.yubico.com/yubikey-val/Getting_Started_Writing_Clients.html
76
     *
77
     * @param Member $member
78
     * @param string $yubiString The Identifier String of the Yubikey
79
     */
80
    private static function updateMember($member, $yubiString)
81
    {
82
        if (!$member->YubiAuthEnabled) {
83
            $member->YubiAuthEnabled = true;
84
        }
85
        if (!$member->Yubikey) {
86
            $member->Yubikey = $yubiString;
87
        }
88
        $member->write();
89
    }
90
91
    /**
92
     * @param null $validation
93
     */
94
    private static function updateForm( $validation = null)
95
    {
96
        $form = self::$form;
97
        if ($form) {
98
            if ($validation == null) {
99
                // Default validation error.
100
                $validation = ValidationResult::create(false,
101
                    _t('YubikeyAuthenticator.ERRORYUBIKEY', 'Yubikey authentication error'));
102
            }
103
            $form->sessionMessage($validation->message(), 'bad');
0 ignored issues
show
Bug introduced by
It seems like $validation is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
104
        }
105
106
    }
107
108
    /**
109
     * Handle login if the user did not enter a Yubikey string.
110
     * Will break out and return NULL if the member should use their Yubikey
111
     *
112
     * @param Member $member
113
     * @return null|Member
114
     */
115
    private static function authenticate_noyubikey($member)
116
    {
117
        $member->NoYubikeyCount += 1;
118
        $member->write();
119
        $maxNoYubi = self::config()->get('MaxNoYubiLogin');
120 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...
121
            $validationError = ValidationResult::create(false,
122
                _t('YubikeyAuthenticator.ERRORMAXYUBIKEY', 'Maximum login without yubikey exceeded'));
123
            self::updateForm($validationError);
124
            $member->registerFailedLogin();
125
126
            return null;
127
        }
128
        $date1 = new DateTime($member->Created);
129
        $date2 = new DateTime(date('Y-m-d'));
130
131
        $diff = $date2->diff($date1)->format("%a");
132
        $maxNoYubiDays = self::config()->get('MaxNoYubiLoginDays');
133
134 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...
135
            $validationError = ValidationResult::create(false,
136
                _t('YubikeyAuthenticator.ERRORMAXYUBIKEYDAYS', 'Maximum days without yubikey exceeded'));
137
            self::updateForm($validationError);
138
            $member->registerFailedLogin();
139
140
            return null;
141
142
        }
143
144
        return $member;
145
    }
146
147
    /**
148
     * Validate a member plus it's yubikey login. It compares the fingerprintt and after that, tries to validate the Yubikey string
149
     * @param array $data
150
     * @param Member $member
151
     * @return null|Member
152
     */
153
    private static function authenticate_yubikey($data, $member)
154
    {
155
        $data['Yubikey'] = strtolower($data['Yubikey']);
156
        $yubiCode = QwertyConvertor::convertString($data['Yubikey']);
157
        $yubiFingerprint = substr($yubiCode, 0, -32);
158
        // If the member has a yubikey ID set, compare it to the fingerprint.
159
        if ($member->Yubikey && strpos($yubiFingerprint, $member->Yubikey) !== 0) {
160
            self::updateForm();
161
162
            return null; // Yubikey id doesn't match the member.
163
        }
164
        $clientID = YUBIAUTH_CLIENTID;
165
        $apiKey = YUBIAUTH_APIKEY;
166
        $service = Injector::inst()->createWithArgs('Yubikey\Validate', array($apiKey, $clientID));
167
        if ($url = self::config()->get('AuthURL')) {
168
            $service->setHost($url);
169
        }
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
}