Passed
Pull Request — master (#2)
by Tim
08:47
created

YubiKey::getYubiKeyPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SimpleSAML\Module\authYubikey\Auth\Source;
4
5
use Exception;
6
use SimpleSAML\Assert\Assert;
7
use SimpleSAML\Auth;
8
use SimpleSAML\Error;
9
use SimpleSAML\Logger;
10
use SimpleSAML\Module;
11
use SimpleSAML\Utils;
12
13
/*
14
 * Copyright (C) 2009  Andreas Åkre Solberg <[email protected]>
15
 * Copyright (C) 2009  Simon Josefsson <[email protected]>.
16
 *
17
 * This file is part of SimpleSAMLphp
18
 *
19
 * SimpleSAMLphp is free software; you can redistribute it and/or
20
 * modify it under the terms of the GNU Lesser General Public License
21
 * as published by the Free Software Foundation; either version 3 of
22
 * the License, or (at your option) any later version.
23
 *
24
 * SimpleSAMLphp is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
27
 * Lesser General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Lesser General Public
30
 * License License along with GNU SASL Library; if not, write to the
31
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
32
 * Boston, MA 02110-1301, USA.
33
 *
34
 */
35
36
/**
37
 * YubiKey authentication module, see http://www.yubico.com/developers/intro/
38
 *
39
 * Configure it by adding an entry to config/authsources.php such as this:
40
 *
41
 *    'yubikey' => array(
42
 *        'authYubiKey:YubiKey',
43
 *        'id' => 997,
44
 *        'key' => 'b64hmackey',
45
 *    ),
46
 *
47
 * To generate your own client id/key you will need one YubiKey, and then
48
 * go to http://yubico.com/developers/api/
49
 *
50
 * @package SimpleSAMLphp
51
 */
52
53
class YubiKey extends Auth\Source
54
{
55
    /**
56
     * The string used to identify our states.
57
     */
58
    public const STAGEID = '\SimpleSAML\Module\authYubiKey\Auth\Source\YubiKey.state';
59
60
    /**
61
     * The number of characters of the OTP that is the secure token.
62
     * The rest is the user id.
63
     */
64
    public const TOKENSIZE = 32;
65
66
    /**
67
     * The key of the AuthId field in the state.
68
     */
69
    public const AUTHID = '\SimpleSAML\Module\authYubiKey\Auth\Source\YubiKey.AuthId';
70
71
    /**
72
     * The client id/key for use with the Auth_Yubico PHP module.
73
     * @var string
74
     */
75
    private $yubi_id;
76
77
    /** @var string */
78
    private $yubi_key;
79
80
81
    /**
82
     * Constructor for this authentication source.
83
     *
84
     * @param array $info  Information about this authentication source.
85
     * @param array $config  Configuration.
86
     */
87
    public function __construct(array $info, array $config)
88
    {
89
        // Call the parent constructor first, as required by the interface
90
        parent::__construct($info, $config);
91
92
        if (array_key_exists('id', $config)) {
93
            $this->yubi_id = $config['id'];
94
        }
95
96
        if (array_key_exists('key', $config)) {
97
            $this->yubi_key = $config['key'];
98
        }
99
    }
100
101
102
    /**
103
     * Initialize login.
104
     *
105
     * This function saves the information about the login, and redirects to a
106
     * login page.
107
     *
108
     * @param array &$state  Information about the current authentication.
109
     */
110
    public function authenticate(array &$state): void
111
    {
112
        // We are going to need the authId in order to retrieve this authentication source later
113
        $state[self::AUTHID] = $this->authId;
114
115
        $id = Auth\State::saveState($state, self::STAGEID);
116
        $url = Module::getModuleURL('authYubiKey/yubikeylogin.php');
117
        $httpUtils = new Utils\HTTP();
118
        $httpUtils->redirectTrustedURL($url, ['AuthState' => $id]);
119
    }
120
121
122
    /**
123
     * Handle login request.
124
     *
125
     * This function is used by the login form (core/www/loginuserpass.php) when the user
126
     * enters a username and password. On success, it will not return. On wrong
127
     * username/password failure, it will return the error code. Other failures will throw an
128
     * exception.
129
     *
130
     * @param string $authStateId  The identifier of the authentication state.
131
     * @param string $otp  The one time password entered-
132
     * @return string|void Error code in the case of an error.
133
     */
134
    public static function handleLogin(string $authStateId, string $otp)
135
    {
136
        /* Retrieve the authentication state. */
137
        $state = Auth\State::loadState($authStateId, self::STAGEID);
138
        if (is_null($state)) {
139
            throw new Error\NoState();
140
        }
141
142
        /* Find authentication source. */
143
        Assert::keyExists($state, self::AUTHID);
144
145
        $source = Auth\Source::getById($state[self::AUTHID]);
146
        Assert::isInstanceOf(
147
            $source,
148
            YubiKey::class,
149
            'Could not find authentication source with id ' . $state[self::AUTHID]
150
        );
151
152
        try {
153
            /**
154
             * Attempt to log in.
155
             *
156
             * @var \SimpleSAML\Module\authYubikey\Auth\Source\YubiKey $source
157
             */
158
            $attributes = $source->login($otp);
159
        } catch (Error\Error $e) {
160
            /* An error occurred during login. Check if it is because of the wrong
161
             * username/password - if it is, we pass that error up to the login form,
162
             * if not, we let the generic error handler deal with it.
163
             */
164
            if ($e->getErrorCode() === 'WRONGUSERPASS') {
165
                return 'WRONGUSERPASS';
166
            }
167
168
            /* Some other error occurred. Rethrow exception and let the generic error
169
             * handler deal with it.
170
             */
171
            throw $e;
172
        }
173
174
        $state['Attributes'] = $attributes;
175
        Auth\Source::completeAuth($state);
176
177
        assert(false);
178
    }
179
180
181
    /**
182
     * Return the user id part of a one time passord
183
     *
184
     * @param string $otp
185
     * @return string
186
     */
187
    public static function getYubiKeyPrefix(string $otp): string
188
    {
189
        $uid = substr($otp, 0, strlen($otp) - self::TOKENSIZE);
190
        return $uid;
191
    }
192
193
194
    /**
195
     * Attempt to log in using the given username and password.
196
     *
197
     * On a successful login, this function should return the users attributes. On failure,
198
     * it should throw an exception. If the error was caused by the user entering the wrong
199
     * username or password, a \SimpleSAML\Error\Error('WRONGUSERPASS') should be thrown.
200
     *
201
     * Note that both the username and the password are UTF-8 encoded.
202
     *
203
     * @param string $otp
204
     * @return array Associative array with the users attributes.
205
     */
206
    protected function login(string $otp): array
207
    {
208
        require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/libextinc/Yubico.php';
209
210
        $yubi = new \Auth_Yubico($this->yubi_id, $this->yubi_key);
211
        try {
212
            $yubi->verify($otp);
213
            $uid = self::getYubiKeyPrefix($otp);
214
            $attributes = ['uid' => [$uid]];
215
        } catch (Exception $e) {
216
            Logger::info(
217
                'YubiKey:' . $this->authId . ': Validation error (otp ' . $otp . '), debug output: '
218
                . $yubi->getLastResponse()
219
            );
220
            throw new Error\Error('WRONGUSERPASS', $e);
221
        }
222
223
        Logger::info(
224
            'YubiKey:' . $this->authId . ': YubiKey otp ' . $otp . ' validated successfully: '
225
            . $yubi->getLastResponse()
226
        );
227
        return $attributes;
228
    }
229
}
230