*/
public static function getIdForURL(string $url)
{
- // Avoid any database requests when the hostname isn't even part of the url.
- if (!strpos($url, DI::baseUrl()->getHostname())) {
+ // Avoid database queries when the local node hostname isn't even part of the url.
+ if (!Contact::isLocal($url)) {
return 0;
}
*/
public static function getIdFromPasswordAuthentication($user_info, $password, $third_party = false)
{
- $user = self::getAuthenticationInfo($user_info);
+ // Addons registered with the "authenticate" hook may create the user on the
+ // fly. `getAuthenticationInfo` will fail if the user doesn't exist yet. If
+ // the user doesn't exist, we should give the addons a chance to create the
+ // user in our database, if applicable, before re-throwing the exception if
+ // they fail.
+ try {
+ $user = self::getAuthenticationInfo($user_info);
+ } catch (Exception $e) {
+ $username = (is_string($user_info) ? $user_info : $user_info['nickname'] ?? '');
+
+ // Addons can create users, and since this 'catch' branch should only
+ // execute if getAuthenticationInfo can't find an existing user, that's
+ // exactly what will happen here. Creating a numeric username would create
+ // abiguity with user IDs, possibly opening up an attack vector.
+ // So let's be very careful about that.
+ if (empty($username) || is_numeric($username)) {
+ throw $e;
+ }
+
+ return self::getIdFromAuthenticateHooks($username, $password);
+ }
if ($third_party && DI::pConfig()->get($user['uid'], '2fa', 'verified')) {
// Third-party apps can't verify two-factor authentication, we use app-specific passwords instead
}
return $user['uid'];
+ } else {
+ return self::getIdFromAuthenticateHooks($user['nickname'], $password); // throws
+ }
+
+ throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed'));
+ }
+
+ /**
+ * Try to obtain a user ID via "authenticate" hook addons
+ *
+ * Returns the user id associated with a successful password authentication
+ *
+ * @param string $username
+ * @param string $password
+ * @return int User Id if authentication is successful
+ * @throws HTTPException\ForbiddenException
+ */
+ public static function getIdFromAuthenticateHooks($username, $password)
+ {
+ $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);
+
+ if ($addon_auth['authenticated'] && $addon_auth['user_record']) {
+ return $addon_auth['user_record']['uid'];
}
throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed'));
if (is_int($user_info)) {
$user = DBA::selectFirst(
'user',
- ['uid', 'password', 'legacy_password'],
+ ['uid', 'nickname', 'password', 'legacy_password'],
[
'uid' => $user_info,
'blocked' => 0,
]
);
} else {
- $fields = ['uid', 'password', 'legacy_password'];
+ $fields = ['uid', 'nickname', 'password', 'legacy_password'];
$condition = [
"(`email` = ? OR `username` = ? OR `nickname` = ?)
AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
Photo::update(['profile' => 1], ['resource-id' => $resource_id]);
}
}
+
+ Contact::updateSelfFromUserID($uid, true);
}
Hook::callAll('register_account', $uid);