strict typing, textual space changes
[fsfdrupalauth.git] / lib / Auth / Source / FSFDrupalAuth.php
CommitLineData
395539d7
AE
1<?php
2
7f0a5f4a
AE
3declare(strict_types=1);
4
395539d7
AE
5namespace SimpleSAML\Module\fsfdrupalauth\Auth\Source;
6
cf44092c
AE
7use Exception;
8use PDO;
9use PDOException;
7f0a5f4a 10use SimpleSAML\Assert\Assert;
cf44092c
AE
11use SimpleSAML\Error;
12use SimpleSAML\Logger;
13
395539d7
AE
14/**
15 * Extension of simple SQL authentication source
16 *
17 * @package SimpleSAMLphp
18 */
19
20class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
21{
22 /**
23 * The DSN we should connect to.
24 */
7f0a5f4a 25 private string $dsn;
395539d7
AE
26
27 /**
28 * The username we should connect to the database with.
29 */
7f0a5f4a 30 private string $username;
395539d7
AE
31
32 /**
33 * The password we should connect to the database with.
34 */
7f0a5f4a 35 private string $password;
395539d7
AE
36
37 /**
38 * The options that we should connect to the database with.
39 */
7f0a5f4a 40 private string $options;
395539d7
AE
41
42 /**
43 * The query we should use to retrieve the attributes for the user.
44 *
45 * The username and password will be available as :username and :password.
46 */
7f0a5f4a
AE
47 private string $query_main;
48 private string $query_membership;
49 private string $query_staff;
794d92ca 50
7f0a5f4a
AE
51 private string $query_nomination_process_donations;
52 private string $query_nomination_process_gift_receipt;
53 private string $query_nomination_process_adhoc;
395539d7 54
7f0a5f4a
AE
55 private string $query_discussion_process_old_membership;
56 private string $query_discussion_process_donations;
57 private string $query_discussion_process_adhoc;
794d92ca 58
c0d116a9 59 /**
e12acfe1
AE
60 * SQL query parameters, or variables that help determine which attributes
61 * someone has
c0d116a9 62 */
7f0a5f4a
AE
63 private string $fsf_org_id;
64 private string $gift_redeem_page_id;
e9db6ecd 65
7f0a5f4a
AE
66 private string $nomination_process_active;
67 private string $nomination_process_contrib_start_date;
68 private string $nomination_process_contrib_end_date;
69 private string $nomination_process_adhoc_access_group_id;
70 private string $membership_monthly_rate;
71 private string $student_membership_monthly_rate;
c0d116a9 72
7f0a5f4a
AE
73 private string $discussion_process_active;
74 private string $discussion_process_contrib_start_date;
75 private string $discussion_process_contrib_end_date;
76 private string $discussion_process_adhoc_access_group_id;
77 private string $discussion_process_adhoc_no_access_group_id;
78 private string $discussion_process_donation_amount;
794d92ca 79
7f0a5f4a 80 private string $discussion_moderator_access_group_id;
0072990f 81
395539d7
AE
82 /**
83 * Constructor for this authentication source.
84 *
85 * @param array $info Information about this authentication source.
86 * @param array $config Configuration.
87 */
7f0a5f4a 88 public function __construct(array $info, array $config)
395539d7 89 {
395539d7
AE
90 // Call the parent constructor first, as required by the interface
91 parent::__construct($info, $config);
92
93 // Make sure that all required parameters are present.
e9db6ecd
AE
94 foreach (['dsn',
95 'username',
96 'password',
97
98 'query_main',
99 'query_membership',
100 'query_staff',
101
e12acfe1 102 'query_nomination_process_donations',
3fa64def 103 'query_nomination_process_gift_receipt',
e9db6ecd
AE
104 'query_nomination_process_adhoc',
105
106 'fsf_org_id',
107 'gift_redeem_page_id',
108
2d61361e 109 'nomination_process_active',
e12acfe1 110 'nomination_process_contrib_start_date',
e9db6ecd
AE
111 'nomination_process_contrib_end_date',
112 'nomination_process_adhoc_access_group_id',
113 'membership_monthly_rate',
794d92ca
AE
114 'student_membership_monthly_rate',
115
1ae1ff15 116 'query_discussion_process_old_membership',
794d92ca 117 'query_discussion_process_donations',
794d92ca
AE
118 'query_discussion_process_adhoc',
119
120 'discussion_process_active',
121 'discussion_process_contrib_start_date',
122 'discussion_process_contrib_end_date',
123 'discussion_process_adhoc_access_group_id',
79ae195e 124 'discussion_process_adhoc_no_access_group_id',
a247e117
AE
125 'discussion_process_donation_amount',
126
127 'discussion_moderator_access_group_id',]
3fa64def 128 as $param) {
e12acfe1 129
395539d7 130 if (!array_key_exists($param, $config)) {
7f0a5f4a
AE
131 throw new Exception('Missing required attribute \'' . $param .
132 '\' for authentication source ' . $this->authId);
395539d7
AE
133 }
134
135 if (!is_string($config[$param])) {
7f0a5f4a
AE
136 throw new Exception('Expected parameter \'' . $param .
137 '\' for authentication source ' . $this->authId .
138 ' to be a string. Instead it was: ' .
395539d7
AE
139 var_export($config[$param], true));
140 }
e12acfe1
AE
141
142 $this->$param = $config[$param];
395539d7
AE
143 }
144
395539d7
AE
145 if (isset($config['options'])) {
146 $this->options = $config['options'];
147 }
148 }
149
150
151 /**
152 * Create a database connection.
153 *
f58b2b6b 154 * @return PDO The database connection.
395539d7 155 */
7f0a5f4a 156 private function connect(): PDO
395539d7
AE
157 {
158 try {
cf44092c
AE
159 $db = new PDO($this->dsn, $this->username, $this->password, $this->options);
160 } catch (PDOException $e) {
161 // Obfuscate the password if it's part of the dsn
162 $obfuscated_dsn = preg_replace('/(user|password)=(.*?([;]|$))/', '${1}=***', $this->dsn);
163
164 throw new Exception('fsfdrupalauth:' . $this->authId . ': - Failed to connect to \'' .
165 $obfuscated_dsn . '\': ' . $e->getMessage());
395539d7
AE
166 }
167
cf44092c 168 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
395539d7
AE
169
170 $driver = explode(':', $this->dsn, 2);
171 $driver = strtolower($driver[0]);
172
173 // Driver specific initialization
174 switch ($driver) {
175 case 'mysql':
176 // Use UTF-8
177 $db->exec("SET NAMES 'utf8mb4'");
178 break;
179 case 'pgsql':
180 // Use UTF-8
181 $db->exec("SET NAMES 'UTF8'");
182 break;
183 }
184
185 return $db;
186 }
187
188 /*
189 * Check the password against a Drupal hash
190 *
191 */
7f0a5f4a 192 private function check_password(string $password, string $hash): boolean {
395539d7
AE
193
194 //
195 // The reason for running a separate process is so that the PHP global
196 // env doesn't get clobbered by include / require.
197 //
198
199 // pipes code based off of https://www.php.net/manual/en/function.proc-open.php
200 // CC-BY 3.0 or later
201 $descriptorspec = array(
59f90414
AE
202 0 => array("pipe", "r"), // stdin is a pipe that the child may read from
203 1 => array("pipe", "w"), // stdout is a pipe that the child may write to
204 2 => array("pipe", "w") // stderr is a pipe that the child may write to
395539d7
AE
205 );
206
207 $cwd = "../modules/fsfdrupalauth/extlib";
208 //$env = array('some_option' => 'aeiou');
209 $env = array();
210
211 $process = proc_open('php drupal-pw-check.php', $descriptorspec, $pipes, $cwd, $env);
212
213 if (is_resource($process)) {
214 // $pipes now looks like this:
215 // 0 => writeable handle connected to child stdin
216 // 1 => readable handle connected to child stdout
217
218 fwrite($pipes[0], json_encode([$password, $hash]));
219 fclose($pipes[0]);
220
221 $result = stream_get_contents($pipes[1]);
222 fclose($pipes[1]);
223
224 $errors = stream_get_contents($pipes[2]);
225 fclose($pipes[2]);
226
227 // It is important that you close any pipes before calling
228 // proc_close in order to avoid a deadlock
229 $return_value = proc_close($process);
230
7f0a5f4a 231 //Logger::debug('fsfdrupalauth:' . $this->authId . ': authenticator stdout: ' . $result);
3fa64def 232
e57e2393 233 $errors_found_yet = false;
395539d7 234 if ($errors != "") {
7f0a5f4a 235 Logger::error('fsfdrupalauth:' . $this->authId.': authenticator stderr: ' . $errors);
e57e2393 236 $errors_found_yet = true;
395539d7 237 }
3fa64def 238
395539d7 239 if ($return_value != 0) {
7f0a5f4a 240 Logger::error('fsfdrupalauth:' . $this->authId . ': authenticator non-zero return code: ' . $return_value);
e57e2393 241 $errors_found_yet = true;
395539d7 242 }
3fa64def 243
6921b9d4 244 return (!$errors_found_yet && is_string($result) && rtrim($result) == "true");
395539d7
AE
245
246 } else {
247
7f0a5f4a 248 Logger::error('fsfdrupalauth:' . $this->authId . ': unable to launch authenticator');
395539d7
AE
249
250 return false;
251 }
252 }
253
254 /**
255 *
256 * query the database with arbitrary queries that only require a user name.
257 *
258 */
7f0a5f4a 259 private function query_db(string $queryname, string $query_params): array
395539d7 260 {
395539d7
AE
261 $db = $this->connect();
262
263 try {
264 $sth = $db->prepare($this->$queryname);
cf44092c 265 } catch (PDOException $e) {
7f0a5f4a
AE
266 throw new Exception('fsfdrupalauth:' . $this->authId .
267 ': - Failed to prepare queryname: ' . $queryname . ': ' . $e->getMessage());
395539d7
AE
268 }
269
270 try {
2e644466 271 $sth->execute($query_params);
cf44092c 272 } catch (PDOException $e) {
7f0a5f4a
AE
273 throw new Exception('fsfdrupalauth:' . $this->authId .
274 ': - Failed to execute queryname: ' . $queryname . ': ' . $e->getMessage());
395539d7
AE
275 }
276
277 try {
f58b2b6b 278 $data = $sth->fetchAll(PDO::FETCH_ASSOC);
cf44092c 279 } catch (PDOException $e) {
7f0a5f4a
AE
280 throw new Exception('fsfdrupalauth:' . $this->authId .
281 ': - Failed to fetch result set: ' . $e->getMessage());
395539d7
AE
282 }
283
7f0a5f4a 284 Logger::info('fsfdrupalauth:' . $this->authId . ': Got ' . count($data) .
395539d7
AE
285 ' rows from database');
286
287 return $data;
288 }
289
290 /**
291 * add more CAS attributes to user, such as is_staff and is_member
292 */
7f0a5f4a 293 private function add_more_attributes(array &$attributes, string $username): void {
395539d7 294
fd2b4a48
AE
295 //
296 // query on staff
297 //
298
299 $staff_data = $this->query_db('query_staff', ['username' => $username, 'fsf_org_id' => $this->fsf_org_id]);
300
301 if (count($staff_data) === 0) {
302 // No rows returned - invalid username
7f0a5f4a 303 Logger::debug('fsfdrupalauth:' . $this->authId .
fd2b4a48
AE
304 ': No rows in result set. Probably not FSF staff.');
305 }
306
307 $attributes['is_fsf_staff'] = ['false'];
308
309 foreach ($staff_data as $row) {
310 foreach ($row as $key => $value) {
311
312 if ($value === null) {
313 continue;
314 }
315 $value = (string) $value;
316
317 if (strtolower($value) === strtolower($username)) {
318 // they are staff
319 $attributes['is_fsf_staff'] = ['true'];
320 break;
321 }
322 }
323 }
324
f33432a6
AE
325 //
326 // query on membership
327 //
395539d7 328
2e644466 329 $membership_data = $this->query_db('query_membership', ['username' => $username]);
395539d7
AE
330
331 if (count($membership_data) === 0) {
332 // No rows returned - invalid username
7f0a5f4a 333 Logger::debug('fsfdrupalauth:' . $this->authId .
395539d7
AE
334 ': No rows in result set. Probably no membership.');
335 }
336
f33432a6 337 $attributes['is_member'] = ['false'];
395539d7
AE
338 $attributes['was_member'] = ['false'];
339
340 foreach ($membership_data as $row) {
341 foreach ($row as $key => $value) {
342 if ($value === null) {
343 continue;
344 }
345 $value = (string) $value;
346
f33432a6 347 if ($value === '1' || $value === '2' || $value === '3') {
395539d7
AE
348 $attributes['is_member'] = ['true'];
349 $attributes['was_member'] = ['true'];
f33432a6 350 } elseif ($value === '4') {
395539d7 351 $attributes['was_member'] = ['true'];
f33432a6 352 }
395539d7
AE
353 }
354 }
355
c0d116a9 356 //
0782e0aa 357 // helper functions for access to board nomination / discussion process
c0d116a9
AE
358 //
359
29854532
AE
360 /**
361 * @param string $query_name Name of query in authsources
f38130e1 362 * @param array $extra_params Associative array of parameters to include in query
29854532 363 */
7f0a5f4a
AE
364 $donation_query = function (string $query_name, array $extra_params): array
365 use (string $username) {
29854532 366
3fa64def 367 $parameters = ['username' => $username];
29854532 368
3fa64def
AE
369 foreach ($extra_params as $key => $value) {
370 $parameters[$key] = $value;
29854532 371 }
78e03f98 372
f38130e1
AE
373 return $this->query_db($query_name, $parameters);
374 };
78e03f98 375
1ae1ff15
AE
376 $old_membership_query = $donation_query;
377
7f0a5f4a 378 $compare_res = function (array $result, int $amount): void {
29854532 379 foreach ($result[0] as $key => $value) {
f38130e1
AE
380 if (intval($value) >= $amount) {
381 return true;
382 }
383 }
384 return false;
385 };
386
32c7abac
AE
387 // set dates here, used by helper functions below
388 $nomination_process_start_date = $this->nomination_process_contrib_start_date;
389 $nomination_process_end_date = $this->nomination_process_contrib_end_date;
390 $discussion_process_start_date = $this->discussion_process_contrib_start_date;
391 $discussion_process_end_date = $this->discussion_process_contrib_end_date;
392
393
f38130e1
AE
394 // looks for memberships / comparable donations in time window. also
395 // looks for a membership or donation (included as a param) that
396 // occurred up to a year before, and that would have carried over into
397 // the time window with a single donation. this approximates whether
398 // the person was, or would have been, a member during the configured
399 // time window.
7f0a5f4a
AE
400 $nomination_process_analyze_history = function (array $selective_donations_history): boolean
401 use (string $nomination_process_start_date, string $nomination_process_end_date) {
f38130e1
AE
402
403 $eligible = false;
404
7f0a5f4a
AE
405 Logger::debug('fsfdrupalauth:' . $this->authId .
406 ': start date: ' . $nomination_process_start_date . " end date: " . $nomination_process_end_date);
21c240cb 407
32c7abac
AE
408 $start_date_obj = new \DateTime($nomination_process_start_date);
409 $end_date_obj = new \DateTime($nomination_process_end_date);
f38130e1
AE
410
411 foreach ($selective_donations_history as $row) {
412
413 $amount = intval($row['amount']);
414 $member_type_id = $row['member_type_id'];
415 $receive_date_obj = new \DateTime($row['receive_date']);
416
417 if ($amount < 5) {
418 continue;
419
420 } elseif ($receive_date_obj >= $start_date_obj and $receive_date_obj <= $end_date_obj) {
421 return true;
422
423 } elseif ($receive_date_obj < $start_date_obj) {
424 switch ($member_type_id) {
425 case '1':
426 case '2':
427 $rate = intval($this->student_membership_monthly_rate);
428 break;
429 case '8':
430 case '9':
431 case null:
432 default:
433 $rate = intval($this->membership_monthly_rate);
434 break;
435 }
436 $membership_end_date_obj = new \DateTime($row['receive_date']);
437 $membership_end_date_obj->add(new \DateInterval("P" . ceil($amount / $rate) . "M"));
438
439 if ($membership_end_date_obj >= $start_date_obj) {
440 return true;
441 }
442 }
78e03f98 443 }
78e03f98
AE
444 return false;
445 };
446
7f0a5f4a
AE
447 $discussion_process_analyze_history = function (array $selective_donations_history): boolean
448 use (string $discussion_process_start_date, string $discussion_process_end_date) {
794d92ca
AE
449
450 $eligible = false;
451 $total = 0;
452
7f0a5f4a
AE
453 Logger::debug('fsfdrupalauth:' . $this->authId .
454 ': start date: ' . $discussion_process_start_date . " end date: " . $discussion_process_end_date);
21c240cb 455
32c7abac
AE
456 $start_date_obj = new \DateTime($discussion_process_start_date);
457 $end_date_obj = new \DateTime($discussion_process_end_date);
794d92ca
AE
458
459 foreach ($selective_donations_history as $row) {
460
461 $amount = intval($row['amount']);
462 $member_type_id = $row['member_type_id'];
463 $receive_date_obj = new \DateTime($row['receive_date']);
464
32c7abac 465 if (($receive_date_obj > $start_date_obj) && ($receive_date_obj < $end_date_obj)) {
794d92ca
AE
466 $total += $amount;
467 }
468 }
469
7f0a5f4a
AE
470 Logger::debug('fsfdrupalauth:' . $this->authId .
471 ': total amount: $' . $total);
21c240cb 472
79ae195e 473 if ($total >= $this->discussion_process_donation_amount) {
794d92ca
AE
474 return true;
475 } else {
476 return false;
477 }
478 };
479
0782e0aa
AE
480 //
481 // nomination form participation specific checks
482 //
483
32c7abac
AE
484 $donation_params = ['start_date' => $nomination_process_start_date, 'end_date' => $nomination_process_end_date];
485 $gift_member_params = ['start_date' => $nomination_process_start_date, 'end_date' => $nomination_process_end_date, 'gift_redeem_page_id' => intval($this->gift_redeem_page_id)];
e9db6ecd 486 $adhoc_params = ['adhoc_access_group_id' => intval($this->nomination_process_adhoc_access_group_id)];
3fa64def 487
16858322 488 if ($this->nomination_process_active != 'true' ) {
7f0a5f4a 489 Logger::debug('fsfdrupalauth:' . $this->authId . ': Nomination board process checks not active');
16858322 490 $attributes['nomination_process'] = ['false'];
0782e0aa 491
16858322 492 } elseif ($compare_res($donation_query('query_nomination_process_adhoc', $adhoc_params), 1)) {
7f0a5f4a 493 Logger::debug('fsfdrupalauth:' . $this->authId . ': In adhoc list of contacts for nomination board process');
16858322
AE
494 $attributes['nomination_process'] = ['true'];
495
496 } elseif ($attributes['is_member'] != ['true']) {
7f0a5f4a 497 Logger::debug('fsfdrupalauth:' . $this->authId . ': Not a current member for nomination board process');
16858322
AE
498 $attributes['nomination_process'] = ['false'];
499
500 } elseif ($nomination_process_analyze_history($donation_query('query_nomination_process_donations', $donation_params))
501 || $compare_res($donation_query('query_nomination_process_gift_receipt', $gift_member_params), 1)) {
502
7f0a5f4a 503 Logger::debug('fsfdrupalauth:' . $this->authId . ': Past membership / donations meet threshold for nomination board process');
16858322 504 $attributes['nomination_process'] = ['true'];
29854532 505
78e03f98 506 } else {
7f0a5f4a 507 Logger::debug('fsfdrupalauth:' . $this->authId . ': Past membership / donations do not meet threshold for nomination board process');
78e03f98
AE
508 $attributes['nomination_process'] = ['false'];
509 }
c0d116a9 510
794d92ca 511 //
3ff2f217 512 // discussion forum participation specific checks
794d92ca
AE
513 //
514
32c7abac 515 $donation_params = ['start_date' => $discussion_process_start_date, 'end_date' => $discussion_process_end_date];
1ae1ff15 516 $old_member_params = $donation_params;
794d92ca
AE
517 $adhoc_params = ['adhoc_access_group_id' => intval($this->discussion_process_adhoc_access_group_id)];
518 $adhoc_params_no = ['adhoc_access_group_id' => intval($this->discussion_process_adhoc_no_access_group_id)];
519
16858322 520 if ($this->discussion_process_active != 'true' ) {
7f0a5f4a 521 Logger::debug('fsfdrupalauth:' . $this->authId . ': Discussion board process checks not active');
16858322 522 $attributes['discussion_process'] = ['false'];
794d92ca 523
16858322 524 } elseif ($compare_res($donation_query('query_discussion_process_adhoc', $adhoc_params_no), 1)) {
7f0a5f4a 525 Logger::debug('fsfdrupalauth:' . $this->authId . ': Nominee not allowed to participate in board discussion process.');
16858322 526 $attributes['discussion_process'] = ['false'];
794d92ca 527
16858322 528 } elseif ($compare_res($donation_query('query_discussion_process_adhoc', $adhoc_params), 1)) {
7f0a5f4a 529 Logger::debug('fsfdrupalauth:' . $this->authId . ': In adhoc list of contacts for discussion board process');
16858322 530 $attributes['discussion_process'] = ['true'];
794d92ca 531
16858322 532 } elseif ($attributes['is_member'] != ['true']) {
7f0a5f4a 533 Logger::debug('fsfdrupalauth :' . $this->authId . ': Not a member, so not eligible for board nominee discussion process.');
16858322
AE
534 $attributes['discussion_process'] = ['false'];
535
1ae1ff15 536 } elseif ($compare_res($old_membership_query('query_discussion_process_old_membership', $old_member_params), 1)
5ba93058 537 || $discussion_process_analyze_history($donation_query('query_discussion_process_donations', $donation_params))) {
16858322 538
7f0a5f4a 539 Logger::debug('fsfdrupalauth:' . $this->authId . ': Past membership / donations meet threshold for discussion board process');
16858322 540 $attributes['discussion_process'] = ['true'];
794d92ca 541
794d92ca 542 } else {
7f0a5f4a 543 Logger::debug('fsfdrupalauth:' . $this->authId . ': Past membership / donations do not meet threshold for discussion board process');
794d92ca
AE
544 $attributes['discussion_process'] = ['false'];
545 }
546
0072990f
AE
547 //
548 // discussion forum moderator early access
549 //
550
551 $adhoc_params = ['adhoc_access_group_id' => intval($this->discussion_moderator_access_group_id)];
552
553 if ($compare_res($donation_query('query_discussion_process_adhoc', $adhoc_params), 1)) {
7f0a5f4a 554 Logger::debug('fsfdrupalauth:' . $this->authId . ': In adhoc list of moderators for board discussion forum');
0072990f
AE
555 $attributes['discussion_moderator'] = ['true'];
556
557 } else {
7f0a5f4a 558 Logger::debug('fsfdrupalauth:' . $this->authId . ': Not in adhoc list of moderators for board discussion forum');
0072990f
AE
559 $attributes['discussion_moderator'] = ['false'];
560 }
561
429471b4
AE
562 //
563 // aggregate attribute
564 //
565
566 $groups_list = '';
567 $first = true;
568 foreach ($attributes as $key => $value) {
569 if ($value == ['true']) {
570 if (!$first) {
571 $groups_list .= ', ';
572 }
573 $groups_list .= $key;
574 $first = false;
575 }
576 }
577
578 $attributes['groups_list'] = [$groups_list];
395539d7
AE
579 }
580
581 /**
582 * Attempt to log in using the given username and password.
583 *
584 * On a successful login, this function should return the users attributes. On failure,
585 * it should throw an exception. If the error was caused by the user entering the wrong
cf44092c 586 * username or password, a Error\Error('WRONGUSERPASS') should be thrown.
395539d7
AE
587 *
588 * Note that both the username and the password are UTF-8 encoded.
589 *
590 * @param string $username The username the user wrote.
591 * @param string $password The password the user wrote.
592 * @return array Associative array with the users attributes.
593 */
7f0a5f4a 594 protected function login(string $username, string $password): array
395539d7 595 {
395539d7
AE
596
597 //// keep this commented when it's not in use. it prints user passwords to the log file
7f0a5f4a 598 //Logger::debug('fsfdrupalauth:' . $this->authId . ': entered password: ' . $password);
395539d7
AE
599
600
2e644466 601 $user_data = $this->query_db('query_main', ['username' => $username]);
395539d7
AE
602
603
604 if (count($user_data) === 0) {
605 // No rows returned - invalid username
7f0a5f4a 606 Logger::error('fsfdrupalauth:' . $this->authId .
395539d7 607 ': No rows in result set. Probably wrong username.');
cf44092c 608 throw new Error\Error('WRONGUSERPASS');
395539d7
AE
609 }
610
611 /* Extract attributes. We allow the resultset to consist of multiple rows. Attributes
612 * which are present in more than one row will become multivalued. null values and
613 * duplicate values will be skipped. All values will be converted to strings.
614 */
615 $attributes = [];
616
f33432a6
AE
617 // use the entered user name so we don't forcibly change it to all
618 // lower case. this is to preserve the behavior of the old cas server,
619 // and to remain compatible with our MW and Discourse sites that are
620 // case sensitive.
621 $attributes['name'][] = $username;
395539d7
AE
622
623 foreach ($user_data as $row) {
624 foreach ($row as $key => $value) {
625 if ($value === null) {
626 continue;
627 }
628
629 $value = (string) $value;
630
631 if (!array_key_exists($key, $attributes)) {
632 $attributes[$key] = [];
633 }
634
635 if (in_array($value, $attributes[$key], true)) {
636 // Value already exists in attribute
637 continue;
638 }
639
640 $attributes[$key][] = $value;
641 }
642 }
643
644 if (!$this->check_password($password, $attributes['pass'][0])) {
cf44092c 645 throw new Error\Error('WRONGUSERPASS');
395539d7
AE
646 }
647
648 unset($attributes['pass']);
649
650
651 $this->add_more_attributes($attributes, $username);
652
653
7f0a5f4a 654 Logger::info('fsfdrupalauth:' . $this->authId . ': Attributes: ' .
395539d7
AE
655 implode(',', array_keys($attributes)));
656
657 return $attributes;
658 }
659}