Passed
Pull Request — master (#6)
by
unknown
02:04
created

PasswordVerify::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 3
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\sqlauth\Auth\Source;
6
7
use SimpleSAML\Logger;
8
use SimpleSAML\Module\sqlauth\Auth\Source\SQL;
9
10
use function array_key_exists;
11
use function array_keys;
12
use function count;
13
use function implode;
14
use function is_null;
15
use function password_verify;
16
17
/**
18
 * Simple SQL authentication source
19
 *
20
 * This class is very much like the SQL class. The major difference is that
21
 * instead of using SHA2 and other functions in the database we use the PHP
22
 * password_verify() function to allow for example PASSWORD_ARGON2ID to be used
23
 * for verification.
24
 *
25
 * While this class has a query parameter as the SQL class does the meaning
26
 * is different. The query for this class should return at least a column
27
 * called passwordhash containing the hashed password which was generated
28
 * for example using
29
 *    password_hash('hello', PASSWORD_ARGON2ID );
30
 *
31
 * Auth only passes if the PHP code below returns true.
32
 *   password_verify($password, row['passwordhash'] );
33
 *
34
 * Unlike the SQL class the username is the only parameter passed to the SQL query,
35
 * the query can not perform password checks, they are performed by the PHP code
36
 * in this class using password_verify().
37
 *
38
 * If there are other columns in the returned data they are assumed to be attributes
39
 * you would like to be returned through SAML.
40
 *
41
 * @package SimpleSAMLphp
42
 */
43
44
class PasswordVerify extends SQL
45
{
46
    /**
47
     * The column in the result set containing the passwordhash.
48
     */
49
    protected string $passwordhashcolumn = 'passwordhash';
50
51
    /**
52
     * Constructor for this authentication source.
53
     *
54
     * @param array $info  Information about this authentication source.
55
     * @param array $config  Configuration.
56
     */
57
    public function __construct(array $info, array $config)
58
    {
59
        // Call the parent constructor first, as required by the interface
60
        parent::__construct($info, $config);
61
62
        if (array_key_exists('passwordhashcolumn', $config)) {
63
            $this->passwordhashcolumn = $config['passwordhashcolumn'];
64
        }
65
    }
66
67
68
69
    /**
70
     * Attempt to log in using the given username and password.
71
     *
72
     * On a successful login, this function should return the users attributes. On failure,
73
     * it should throw an exception. If the error was caused by the user entering the wrong
74
     * username or password, a \SimpleSAML\Error\Error('WRONGUSERPASS') should be thrown.
75
     *
76
     * Note that both the username and the password are UTF-8 encoded.
77
     *
78
     * @param string $username  The username the user wrote.
79
     * @param string $password  The password the user wrote.
80
     * @return array  Associative array with the users attributes.
81
     */
82
    protected function login(string $username, string $password): array
83
    {
84
        $this->verifyUserNameWithRegex($username);
85
86
        $db = $this->connect();
87
        $params = ['username' => $username];
88
        $attributes = [];
89
90
        $numQueries = count($this->query);
91
        for ($x = 0; $x < $numQueries; $x++) {
92
            $data = $this->executeQuery($db, $this->query[$x], $params);
93
94
            Logger::info('sqlauth:' . $this->authId . ': Got ' . count($data) .
95
                         ' rows from database');
96
97
            if ($x === 0) {
98
                if (count($data) === 0) {
99
                    // No rows returned - invalid username/password
100
                    Logger::error('sqlauth:' . $this->authId .
101
                                  ': No rows in result set. Probably wrong username/password.');
102
                    throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
103
                }
104
            }
105
106
107
            /**
108
             * Sanity check, passwordhash must be in each resulting tuple and must have
109
             * the same value in every tuple.
110
             *
111
             * Note that $pwhash will contain the passwordhash value after this loop.
112
             */
113
            $pwhash = null;
114
            foreach ($data as $row) {
115
                if (
116
                    !array_key_exists($this->passwordhashcolumn, $row)
117
                    || is_null($row[$this->passwordhashcolumn])
118
                ) {
119
                    Logger::error('sqlauth:' . $this->authId .
120
                                  ': column ' . $this->passwordhashcolumn . ' must be in every result tuple.');
121
                    throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
122
                }
123
                if ($pwhash) {
124
                    if ($pwhash != $row[$this->passwordhashcolumn]) {
125
                        Logger::error('sqlauth:' . $this->authId .
126
                                      ': column ' . $this->passwordhashcolumn . ' must be THE SAME in every result tuple.');
127
                        throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
128
                    }
129
                }
130
                $pwhash = $row[$this->passwordhashcolumn];
131
            }
132
            /**
133
             * This should never happen as the count(data) test above would have already thrown.
134
             * But checking twice doesn't hurt.
135
             */
136
            if (is_null($pwhash)) {
137
                if ($pwhash != $row[$this->passwordhashcolumn]) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $row does not seem to be defined for all execution paths leading up to this point.
Loading history...
138
                    Logger::error('sqlauth:' . $this->authId .
139
                                  ': column ' . $this->passwordhashcolumn . ' does not contain a password hash.');
140
                    throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
141
                }
142
            }
143
144
            /**
145
             * VERIFICATION!
146
             * Now to check if the password the user supplied is actually valid
147
             */
148
            if (!password_verify($password, $pwhash)) {
149
                Logger::error('sqlauth:' . $this->authId . ': password is incorrect.');
150
                throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
151
            }
152
153
154
            $this->extractAttributes($attributes, $data, [$this->passwordhashcolumn]);
155
        }
156
157
        Logger::info('sqlauth:' . $this->authId . ': Attributes: ' .
158
                     implode(',', array_keys($attributes)));
159
160
        return $attributes;
161
    }
162
}
163