diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php
index ca60cc87a1..6aef31a546 100644
--- a/mod/dfrn_poll.php
+++ b/mod/dfrn_poll.php
@@ -5,6 +5,7 @@
*/
use Friendica\App;
+use Friendica\Core\Authentication;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
@@ -20,7 +21,7 @@ use Friendica\Util\XML;
function dfrn_poll_init(App $a)
{
- Login::sessionAuth();
+ Authentication::sessionAuth();
$dfrn_id = $_GET['dfrn_id'] ?? '';
$type = ($_GET['type'] ?? '') ?: 'data';
diff --git a/src/App.php b/src/App.php
index bbf980d17b..4d652496bf 100644
--- a/src/App.php
+++ b/src/App.php
@@ -8,6 +8,7 @@ use Exception;
use Friendica\App\Arguments;
use Friendica\App\BaseURL;
use Friendica\App\Page;
+use Friendica\Core\Authentication;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Core\Config\Configuration;
use Friendica\Core\Config\PConfiguration;
@@ -717,7 +718,7 @@ class App
Model\Profile::openWebAuthInit($token);
}
- Login::sessionAuth();
+ Authentication::sessionAuth();
if (empty($_SESSION['authenticated'])) {
header('X-Account-Management-Status: none');
diff --git a/src/Core/Authentication.php b/src/Core/Authentication.php
index 6d017664aa..6ea727871b 100644
--- a/src/Core/Authentication.php
+++ b/src/Core/Authentication.php
@@ -8,13 +8,226 @@ namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
+use Friendica\Database\DBA;
+use Friendica\Model\User;
use Friendica\Network\HTTPException\ForbiddenException;
+use Friendica\Util\DateTimeFormat;
+use Friendica\Util\Network;
+use Friendica\Util\Strings;
/**
* Handle Authentification, Session and Cookies
*/
class Authentication extends BaseObject
{
+ /**
+ * Attempts to authenticate using OpenId
+ *
+ * @param string $openid_url OpenID URL string
+ * @param bool $remember Whether to set the session remember flag
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function openIdAuthentication($openid_url, $remember)
+ {
+ $noid = Config::get('system', 'no_openid');
+
+ $a = self::getApp();
+
+ // if it's an email address or doesn't resolve to a URL, fail.
+ if ($noid || strpos($openid_url, '@') || !Network::isUrlValid($openid_url)) {
+ notice(L10n::t('Login failed.') . EOL);
+ $a->internalRedirect();
+ // NOTREACHED
+ }
+
+ // Otherwise it's probably an openid.
+ try {
+ $openid = new LightOpenID($a->getHostName());
+ $openid->identity = $openid_url;
+ Session::set('openid', $openid_url);
+ Session::set('remember', $remember);
+ $openid->returnUrl = $a->getBaseURL(true) . '/openid';
+ $openid->optional = ['namePerson/friendly', 'contact/email', 'namePerson', 'namePerson/first', 'media/image/aspect11', 'media/image/default'];
+ System::externalRedirect($openid->authUrl());
+ } catch (Exception $e) {
+ notice(L10n::t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '
' . L10n::t('The error message was:') . ' ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Attempts to authenticate using login/password
+ *
+ * @param string $username User name
+ * @param string $password Clear password
+ * @param bool $remember Whether to set the session remember flag
+ * @param string $openid_identity OpenID identity
+ * @param string $openid_server OpenID URL
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function passwordAuthentication($username, $password, $remember, $openid_identity, $openid_server)
+ {
+ $record = null;
+
+ $addon_auth = [
+ 'username' => $username,
+ 'password' => $password,
+ 'authenticated' => 0,
+ 'user_record' => null
+ ];
+
+ $a = self::getApp();
+
+ /*
+ * 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 {
+ if ($addon_auth['authenticated']) {
+ $record = $addon_auth['user_record'];
+
+ if (empty($record)) {
+ throw new Exception(L10n::t('Login failed.'));
+ }
+ } else {
+ $record = DBA::selectFirst(
+ 'user',
+ [],
+ ['uid' => User::getIdFromPasswordAuthentication($username, $password)]
+ );
+ }
+ } catch (Exception $e) {
+ Logger::warning('authenticate: failed login attempt', ['action' => 'login', 'username' => Strings::escapeTags($username), 'ip' => $_SERVER['REMOTE_ADDR']]);
+ info('Login failed. Please check your credentials.' . EOL);
+ $a->internalRedirect();
+ }
+
+ if (!$remember) {
+ Authentication::setCookie(0); // 0 means delete on browser exit
+ }
+
+ // if we haven't failed up this point, log them in.
+ Session::set('remember', $remember);
+ Session::set('last_login_date', DateTimeFormat::utcNow());
+
+ if (!empty($openid_identity) || !empty($openid_server)) {
+ DBA::update('user', ['openid' => $openid_identity, 'openidserver' => $openid_server], ['uid' => $record['uid']]);
+ }
+
+ Session::setAuthenticatedForUser($a, $record, true, true);
+
+ $return_path = Session::get('return_path', '');
+ Session::remove('return_path');
+
+ $a->internalRedirect($return_path);
+ }
+
+ /**
+ * @brief Tries to auth the user from the cookie or session
+ *
+ * @todo Should be moved to Friendica\Core\Session when it's created
+ */
+ public static function sessionAuth()
+ {
+ $a = self::getApp();
+
+ // When the "Friendica" cookie is set, take the value to authenticate and renew the cookie.
+ if (isset($_COOKIE["Friendica"])) {
+ $data = json_decode($_COOKIE["Friendica"]);
+ if (isset($data->uid)) {
+
+ $user = DBA::selectFirst(
+ 'user',
+ [],
+ [
+ 'uid' => $data->uid,
+ 'blocked' => false,
+ 'account_expired' => false,
+ 'account_removed' => false,
+ 'verified' => true,
+ ]
+ );
+ if (DBA::isResult($user)) {
+ if (!hash_equals(
+ Authentication::getCookieHashForUser($user),
+ $data->hash
+ )) {
+ Logger::log("Hash for user " . $data->uid . " doesn't fit.");
+ Authentication::deleteSession();
+ $a->internalRedirect();
+ }
+
+ // Renew the cookie
+ // Expires after 7 days by default,
+ // can be set via system.auth_cookie_lifetime
+ $authcookiedays = Config::get('system', 'auth_cookie_lifetime', 7);
+ Authentication::setCookie($authcookiedays * 24 * 60 * 60, $user);
+
+ // Do the authentification if not done by now
+ if (!isset($_SESSION) || !isset($_SESSION['authenticated'])) {
+ Session::setAuthenticatedForUser($a, $user);
+
+ if (Config::get('system', 'paranoia')) {
+ $_SESSION['addr'] = $data->ip;
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($_SESSION['authenticated'])) {
+ if (!empty($_SESSION['visitor_id']) && empty($_SESSION['uid'])) {
+ $contact = DBA::selectFirst('contact', [], ['id' => $_SESSION['visitor_id']]);
+ if (DBA::isResult($contact)) {
+ self::getApp()->contact = $contact;
+ }
+ }
+
+ if (!empty($_SESSION['uid'])) {
+ // already logged in user returning
+ $check = Config::get('system', 'paranoia');
+ // extra paranoia - if the IP changed, log them out
+ if ($check && ($_SESSION['addr'] != $_SERVER['REMOTE_ADDR'])) {
+ Logger::log('Session address changed. Paranoid setting in effect, blocking session. ' .
+ $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']);
+ Authentication::deleteSession();
+ $a->internalRedirect();
+ }
+
+ $user = DBA::selectFirst(
+ 'user',
+ [],
+ [
+ 'uid' => $_SESSION['uid'],
+ 'blocked' => false,
+ 'account_expired' => false,
+ 'account_removed' => false,
+ 'verified' => true,
+ ]
+ );
+ if (!DBA::isResult($user)) {
+ Authentication::deleteSession();
+ $a->internalRedirect();
+ }
+
+ // Make sure to refresh the last login time for the user if the user
+ // stays logged in for a long time, e.g. with "Remember Me"
+ $login_refresh = false;
+ if (empty($_SESSION['last_login_date'])) {
+ $_SESSION['last_login_date'] = DateTimeFormat::utcNow();
+ }
+ if (strcmp(DateTimeFormat::utc('now - 12 hours'), $_SESSION['last_login_date']) > 0) {
+ $_SESSION['last_login_date'] = DateTimeFormat::utcNow();
+ $login_refresh = true;
+ }
+
+ Session::setAuthenticatedForUser($a, $user, false, false, $login_refresh);
+ }
+ }
+ }
+
/**
* @brief Calculate the hash that is needed for the "Friendica" cookie
*
diff --git a/src/Module/Login.php b/src/Module/Login.php
index c84af17ccb..f43bd221e8 100644
--- a/src/Module/Login.php
+++ b/src/Module/Login.php
@@ -6,22 +6,14 @@
namespace Friendica\Module;
-use Exception;
use Friendica\BaseModule;
use Friendica\Core\Authentication;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
-use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
-use Friendica\Core\System;
-use Friendica\Database\DBA;
-use Friendica\Model\User;
-use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Network;
use Friendica\Util\Strings;
-use LightOpenID;
/**
* Login module
@@ -58,11 +50,11 @@ class Login extends BaseModule
) {
$openid_url = trim(($_POST['openid_url'] ?? '') ?: $_POST['username']);
- self::openIdAuthentication($openid_url, !empty($_POST['remember']));
+ Authentication::openIdAuthentication($openid_url, !empty($_POST['remember']));
}
if (!empty($_POST['auth-params']) && $_POST['auth-params'] === 'login') {
- self::passwordAuthentication(
+ Authentication::passwordAuthentication(
trim($_POST['username']),
trim($_POST['password']),
!empty($_POST['remember']),
@@ -72,214 +64,6 @@ class Login extends BaseModule
}
}
- /**
- * Attempts to authenticate using OpenId
- *
- * @param string $openid_url OpenID URL string
- * @param bool $remember Whether to set the session remember flag
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- private static function openIdAuthentication($openid_url, $remember)
- {
- $noid = Config::get('system', 'no_openid');
-
- $a = self::getApp();
-
- // if it's an email address or doesn't resolve to a URL, fail.
- if ($noid || strpos($openid_url, '@') || !Network::isUrlValid($openid_url)) {
- notice(L10n::t('Login failed.') . EOL);
- $a->internalRedirect();
- // NOTREACHED
- }
-
- // Otherwise it's probably an openid.
- try {
- $openid = new LightOpenID($a->getHostName());
- $openid->identity = $openid_url;
- Session::set('openid', $openid_url);
- Session::set('remember', $remember);
- $openid->returnUrl = $a->getBaseURL(true) . '/openid';
- $openid->optional = ['namePerson/friendly', 'contact/email', 'namePerson', 'namePerson/first', 'media/image/aspect11', 'media/image/default'];
- System::externalRedirect($openid->authUrl());
- } catch (Exception $e) {
- notice(L10n::t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '
' . L10n::t('The error message was:') . ' ' . $e->getMessage());
- }
- }
-
- /**
- * Attempts to authenticate using login/password
- *
- * @param string $username User name
- * @param string $password Clear password
- * @param bool $remember Whether to set the session remember flag
- * @param string $openid_identity OpenID identity
- * @param string $openid_server OpenID URL
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- private static function passwordAuthentication($username, $password, $remember, $openid_identity, $openid_server)
- {
- $record = null;
-
- $addon_auth = [
- 'username' => $username,
- 'password' => $password,
- 'authenticated' => 0,
- 'user_record' => null
- ];
-
- $a = self::getApp();
-
- /*
- * 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 {
- if ($addon_auth['authenticated']) {
- $record = $addon_auth['user_record'];
-
- if (empty($record)) {
- throw new Exception(L10n::t('Login failed.'));
- }
- } else {
- $record = DBA::selectFirst(
- 'user',
- [],
- ['uid' => User::getIdFromPasswordAuthentication($username, $password)]
- );
- }
- } catch (Exception $e) {
- Logger::warning('authenticate: failed login attempt', ['action' => 'login', 'username' => Strings::escapeTags($username), 'ip' => $_SERVER['REMOTE_ADDR']]);
- info('Login failed. Please check your credentials.' . EOL);
- $a->internalRedirect();
- }
-
- if (!$remember) {
- Authentication::setCookie(0); // 0 means delete on browser exit
- }
-
- // if we haven't failed up this point, log them in.
- Session::set('remember', $remember);
- Session::set('last_login_date', DateTimeFormat::utcNow());
-
- if (!empty($openid_identity) || !empty($openid_server)) {
- DBA::update('user', ['openid' => $openid_identity, 'openidserver' => $openid_server], ['uid' => $record['uid']]);
- }
-
- Session::setAuthenticatedForUser($a, $record, true, true);
-
- $return_path = Session::get('return_path', '');
- Session::remove('return_path');
-
- $a->internalRedirect($return_path);
- }
-
- /**
- * @brief Tries to auth the user from the cookie or session
- *
- * @todo Should be moved to Friendica\Core\Session when it's created
- */
- public static function sessionAuth()
- {
- $a = self::getApp();
-
- // When the "Friendica" cookie is set, take the value to authenticate and renew the cookie.
- if (isset($_COOKIE["Friendica"])) {
- $data = json_decode($_COOKIE["Friendica"]);
- if (isset($data->uid)) {
-
- $user = DBA::selectFirst(
- 'user',
- [],
- [
- 'uid' => $data->uid,
- 'blocked' => false,
- 'account_expired' => false,
- 'account_removed' => false,
- 'verified' => true,
- ]
- );
- if (DBA::isResult($user)) {
- if (!hash_equals(
- Authentication::getCookieHashForUser($user),
- $data->hash
- )) {
- Logger::log("Hash for user " . $data->uid . " doesn't fit.");
- Authentication::deleteSession();
- $a->internalRedirect();
- }
-
- // Renew the cookie
- // Expires after 7 days by default,
- // can be set via system.auth_cookie_lifetime
- $authcookiedays = Config::get('system', 'auth_cookie_lifetime', 7);
- Authentication::setCookie($authcookiedays * 24 * 60 * 60, $user);
-
- // Do the authentification if not done by now
- if (!isset($_SESSION) || !isset($_SESSION['authenticated'])) {
- Session::setAuthenticatedForUser($a, $user);
-
- if (Config::get('system', 'paranoia')) {
- $_SESSION['addr'] = $data->ip;
- }
- }
- }
- }
- }
-
- if (!empty($_SESSION['authenticated'])) {
- if (!empty($_SESSION['visitor_id']) && empty($_SESSION['uid'])) {
- $contact = DBA::selectFirst('contact', [], ['id' => $_SESSION['visitor_id']]);
- if (DBA::isResult($contact)) {
- self::getApp()->contact = $contact;
- }
- }
-
- if (!empty($_SESSION['uid'])) {
- // already logged in user returning
- $check = Config::get('system', 'paranoia');
- // extra paranoia - if the IP changed, log them out
- if ($check && ($_SESSION['addr'] != $_SERVER['REMOTE_ADDR'])) {
- Logger::log('Session address changed. Paranoid setting in effect, blocking session. ' .
- $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']);
- Authentication::deleteSession();
- $a->internalRedirect();
- }
-
- $user = DBA::selectFirst(
- 'user',
- [],
- [
- 'uid' => $_SESSION['uid'],
- 'blocked' => false,
- 'account_expired' => false,
- 'account_removed' => false,
- 'verified' => true,
- ]
- );
- if (!DBA::isResult($user)) {
- Authentication::deleteSession();
- $a->internalRedirect();
- }
-
- // Make sure to refresh the last login time for the user if the user
- // stays logged in for a long time, e.g. with "Remember Me"
- $login_refresh = false;
- if (empty($_SESSION['last_login_date'])) {
- $_SESSION['last_login_date'] = DateTimeFormat::utcNow();
- }
- if (strcmp(DateTimeFormat::utc('now - 12 hours'), $_SESSION['last_login_date']) > 0) {
- $_SESSION['last_login_date'] = DateTimeFormat::utcNow();
- $login_refresh = true;
- }
-
- Session::setAuthenticatedForUser($a, $user, false, false, $login_refresh);
- }
- }
- }
-
/**
* @brief Wrapper for adding a login box.
*