Merge pull request #10275 from very-ape/authenticate-hook

Move the 'authenticate' hook deeper into the authentication flow so t…
This commit is contained in:
Hypolite Petovan 2021-05-19 16:51:15 -04:00 committed by GitHub
commit 09cf32926d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 34 deletions

View File

@ -31,7 +31,7 @@ Here's the structure:
* Status: {Unsupported|Arbitrary status} * Status: {Unsupported|Arbitrary status}
*/ */
``` ```
You can also provide a longer documentation in a `README` or `README.md` file. You can also provide a longer documentation in a `README` or `README.md` file.
The latter will be converted from Markdown to HTML in the addon detail page. The latter will be converted from Markdown to HTML in the addon detail page.
@ -447,7 +447,7 @@ Form field array structure is:
- **field**: Standard field data structure to be used by `field_checkbox.tpl` and `field_select.tpl`. - **field**: Standard field data structure to be used by `field_checkbox.tpl` and `field_select.tpl`.
For `checkbox`, **field** is: For `checkbox`, **field** is:
- [0] (String): Form field name; Mandatory. - [0] (String): Form field name; Mandatory.
- [1]: (String): Form field label; Optional, default is none. - [1]: (String): Form field label; Optional, default is none.
- [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false. - [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false.
- [3]: (String): Additional help text; Optional, default is none. - [3]: (String): Additional help text; Optional, default is none.
@ -458,7 +458,7 @@ For `select`, **field** is:
- [1] (String): Form field label; Optional, default is none. - [1] (String): Form field label; Optional, default is none.
- [2] (Boolean): Default value to be selected by default; Optional, default is none. - [2] (Boolean): Default value to be selected by default; Optional, default is none.
- [3] (String): Additional help text; Optional, default is none. - [3] (String): Additional help text; Optional, default is none.
- [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory. - [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory.
### route_collection ### route_collection
Called just before dispatching the router. Called just before dispatching the router.
@ -470,7 +470,7 @@ Hook data is a `\FastRoute\RouterCollector` object that should be used to add ad
Called before trying to detect the target network of a URL. Called before trying to detect the target network of a URL.
If any registered hook function sets the `result` key of the hook data array, it will be returned immediately. If any registered hook function sets the `result` key of the hook data array, it will be returned immediately.
Hook functions should also return immediately if the hook data contains an existing result. Hook functions should also return immediately if the hook data contains an existing result.
Hook data: Hook data:
@ -666,8 +666,13 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('event_updated', $event['id']); Hook::callAll('event_updated', $event['id']);
Hook::callAll("event_created", $event['id']); Hook::callAll("event_created", $event['id']);
### src/Model/Register.php
Hook::callAll('authenticate', $addon_auth);
### src/Model/User.php ### src/Model/User.php
Hook::callAll('authenticate', $addon_auth);
Hook::callAll('register_account', $uid); Hook::callAll('register_account', $uid);
Hook::callAll('remove_user', $user); Hook::callAll('remove_user', $user);
@ -675,6 +680,22 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('lockview_content', $item); Hook::callAll('lockview_content', $item);
### src/Module/Settings/Delegation.php
Hook::callAll('authenticate', $addon_auth);
### src/Module/Settings/TwoFactor/Index.php
Hook::callAll('authenticate', $addon_auth);
### src/Security/Authenticate.php
Hook::callAll('authenticate', $addon_auth);
### src/Security/ExAuth.php
Hook::callAll('authenticate', $addon_auth);
### src/Content/ContactBlock.php ### src/Content/ContactBlock.php
Hook::callAll('contact_block_end', $arr); Hook::callAll('contact_block_end', $arr);
@ -713,7 +734,7 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
### src/Core/Authentication.php ### src/Core/Authentication.php
Hook::callAll('logged_in', $a->user); Hook::callAll('logged_in', $a->user);
### src/Core/StorageManager ### src/Core/StorageManager
Hook::callAll('storage_instance', $data); Hook::callAll('storage_instance', $data);

View File

@ -544,6 +544,24 @@ class User
} }
return $user['uid']; return $user['uid'];
} else {
$addon_auth = [
'username' => $user['nickname'],
'password' => $password,
'authenticated' => 0,
'user_record' => null
];
/*
* An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record
* Addons should never set 'authenticated' except to indicate success - as hooks may be chained
* and later addons should not interfere with an earlier one that succeeded.
*/
Hook::callAll('authenticate', $addon_auth);
if ($addon_auth['authenticated'] && $addon_auth['user_record']) {
return $user['uid'];
}
} }
throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed')); throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed'));
@ -584,7 +602,7 @@ class User
if (is_int($user_info)) { if (is_int($user_info)) {
$user = DBA::selectFirst( $user = DBA::selectFirst(
'user', 'user',
['uid', 'password', 'legacy_password'], ['uid', 'nickname', 'password', 'legacy_password'],
[ [
'uid' => $user_info, 'uid' => $user_info,
'blocked' => 0, 'blocked' => 0,
@ -594,7 +612,7 @@ class User
] ]
); );
} else { } else {
$fields = ['uid', 'password', 'legacy_password']; $fields = ['uid', 'nickname', 'password', 'legacy_password'];
$condition = [ $condition = [
"(`email` = ? OR `username` = ? OR `nickname` = ?) "(`email` = ? OR `username` = ? OR `nickname` = ?)
AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`", AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",

View File

@ -240,34 +240,12 @@ class Authentication
{ {
$record = null; $record = null;
$addon_auth = [
'username' => $username,
'password' => $password,
'authenticated' => 0,
'user_record' => null
];
/*
* An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record
* Addons should never set 'authenticated' except to indicate success - as hooks may be chained
* and later addons should not interfere with an earlier one that succeeded.
*/
Hook::callAll('authenticate', $addon_auth);
try { try {
if ($addon_auth['authenticated']) { $record = $this->dba->selectFirst(
$record = $addon_auth['user_record']; 'user',
[],
if (empty($record)) { ['uid' => User::getIdFromPasswordAuthentication($username, $password)]
throw new Exception($this->l10n->t('Login failed.')); );
}
} else {
$record = $this->dba->selectFirst(
'user',
[],
['uid' => User::getIdFromPasswordAuthentication($username, $password)]
);
}
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->warning('authenticate: failed login attempt', ['action' => 'login', 'username' => Strings::escapeTags($username), 'ip' => $_SERVER['REMOTE_ADDR']]); $this->logger->warning('authenticate: failed login attempt', ['action' => 'login', 'username' => Strings::escapeTags($username), 'ip' => $_SERVER['REMOTE_ADDR']]);
notice($this->l10n->t('Login failed. Please check your credentials.')); notice($this->l10n->t('Login failed. Please check your credentials.'));