From 55cec6c61d8bcd449d3281c0667eacd0252f539b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 27 May 2024 04:33:28 +0000 Subject: [PATCH] OpenWebAuth moved to a separate class / Improved authentication handling --- src/App.php | 28 +- src/Content/Nav.php | 4 +- src/Content/Widget.php | 4 +- .../Capability/IHandleUserSessions.php | 18 +- src/Core/Session/Model/UserSession.php | 14 +- src/Model/Profile.php | 219 --------------- src/Module/Contact/Follow.php | 4 +- src/Module/Directory.php | 3 +- src/Module/Photo.php | 6 +- src/Module/Register.php | 10 +- src/Security/Authentication.php | 95 ++++--- src/Security/OpenWebAuth.php | 252 ++++++++++++++++++ tests/src/Core/Session/UserSessionTest.php | 108 ++++++++ view/lang/C/messages.po | 240 ++++++++--------- 14 files changed, 595 insertions(+), 410 deletions(-) create mode 100644 src/Security/OpenWebAuth.php diff --git a/src/App.php b/src/App.php index 96b8d8b947..6673ec7914 100644 --- a/src/App.php +++ b/src/App.php @@ -39,11 +39,10 @@ use Friendica\Core\L10n; use Friendica\Core\System; use Friendica\Core\Theme; use Friendica\Database\Database; -use Friendica\Model\Contact; -use Friendica\Model\Profile; use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Network\HTTPException; use Friendica\Protocol\ATProtocol\DID; +use Friendica\Security\OpenWebAuth; use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPInputData; use Friendica\Util\HTTPSignature; @@ -94,6 +93,9 @@ class App /** @var string The name of the current mobile theme */ private $currentMobileTheme; + /** @var Authentication */ + private $auth; + /** * @var IManageConfigValues The config */ @@ -279,8 +281,9 @@ class App * @param DbaDefinition $dbaDefinition * @param ViewDefinition $viewDefinition */ - public function __construct(Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition) + public function __construct(Authentication $auth, Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition) { + $this->auth = $auth; $this->database = $database; $this->config = $config; $this->mode = $mode; @@ -563,7 +566,7 @@ class App if ($this->mode->isNormal() && !$this->mode->isBackend()) { $requester = HTTPSignature::getSigner('', $server); if (!empty($requester)) { - Profile::addVisitorCookieForHandle($requester); + OpenWebAuth::addVisitorCookieForHandle($requester); } } @@ -573,17 +576,8 @@ class App // Valid profile links contain a path with "/profile/" and no query parameters if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') && strpos(parse_url($_GET['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) { - if ($this->session->get('visitor_home') != $_GET['zrl']) { - $this->session->set('my_url', $_GET['zrl']); - $this->session->set('authenticated', 0); - - $remote_contact = Contact::getByURL($_GET['zrl'], false, ['subscribe']); - if (!empty($remote_contact['subscribe'])) { - $_SESSION['remote_comment'] = $remote_contact['subscribe']; - } - } - - Model\Profile::zrlInit(); + $this->auth->setUnauthenticatedVisitor($_GET['zrl']); + OpenWebAuth::zrlInit(); } else { // Someone came with an invalid parameter, maybe as a DDoS attempt // We simply stop processing here @@ -594,14 +588,14 @@ class App if (!empty($_GET['owt']) && $this->mode->isNormal()) { $token = $_GET['owt']; - Model\Profile::openWebAuthInit($token); + OpenWebAuth::init($token); } if (!$this->mode->isBackend()) { $auth->withSession($this); } - if (empty($_SESSION['authenticated'])) { + if ($this->session->isUnauthenticated()) { header('X-Account-Management-Status: none'); } diff --git a/src/Content/Nav.php b/src/Content/Nav.php index 3828a4954c..aa8c3f83b9 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -30,12 +30,12 @@ use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Database\Database; use Friendica\Model\Contact; -use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Module\Conversation\Community; use Friendica\Module\Home; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; +use Friendica\Security\OpenWebAuth; class Nav { @@ -281,7 +281,7 @@ class Nav $gdirpath = 'directory'; if ($this->config->get('system', 'singleuser') && $this->config->get('system', 'directory')) { - $gdirpath = Profile::zrl($this->config->get('system', 'directory'), true); + $gdirpath = OpenWebAuth::getZrlUrl($this->config->get('system', 'directory'), true); } if (Feature::isEnabled($this->session->getLocalUserId(), Feature::COMMUNITY) && (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) && diff --git a/src/Content/Widget.php b/src/Content/Widget.php index 8550e44a5e..1108af5124 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -32,7 +32,7 @@ use Friendica\Model\Contact; use Friendica\Model\Circle; use Friendica\Model\Item; use Friendica\Model\Post; -use Friendica\Model\Profile; +use Friendica\Security\OpenWebAuth; use Friendica\Util\DateTimeFormat; use Friendica\Util\Temporal; @@ -85,7 +85,7 @@ class Widget $nv['random'] = DI::l10n()->t('Random Profile'); $nv['inv'] = DI::l10n()->t('Invite Friends'); $nv['directory'] = DI::l10n()->t('Global Directory'); - $nv['global_dir'] = Profile::zrl($global_dir, true); + $nv['global_dir'] = OpenWebAuth::getZrlUrl($global_dir, true); $nv['local_directory'] = DI::l10n()->t('Local Directory'); $aside = []; diff --git a/src/Core/Session/Capability/IHandleUserSessions.php b/src/Core/Session/Capability/IHandleUserSessions.php index fc7e06b783..83ebad502b 100644 --- a/src/Core/Session/Capability/IHandleUserSessions.php +++ b/src/Core/Session/Capability/IHandleUserSessions.php @@ -80,9 +80,9 @@ interface IHandleUserSessions extends IHandleSessions public function getMyUrl(): string; /** - * Returns if the current visitor is authenticated + * Returns if the current visitor is a local user * - * @return bool "true" when visitor is either a local or remote user + * @return bool "true" when visitor is a local user */ public function isAuthenticated(): bool; @@ -100,6 +100,20 @@ interface IHandleUserSessions extends IHandleSessions */ public function isModerator(): bool; + /** + * Returns if the current visitor is a verified remote user + * + * @return bool "true" when visitor is a verified remote user + */ + public function isVisitor(): bool; + + /** + * Returns if the current visitor is an unauthenticated user + * + * @return bool "true" when visitor is an unauthenticated user + */ + public function isUnauthenticated(): bool; + /** * Returns User ID of the managed user in case it's a different identity * diff --git a/src/Core/Session/Model/UserSession.php b/src/Core/Session/Model/UserSession.php index 0b1a197e70..4108c79177 100644 --- a/src/Core/Session/Model/UserSession.php +++ b/src/Core/Session/Model/UserSession.php @@ -130,7 +130,7 @@ class UserSession implements IHandleUserSessions /** {@inheritDoc} */ public function isAuthenticated(): bool { - return $this->session->get('authenticated', false); + return $this->session->get('authenticated', false) && $this->getLocalUserId(); } /** {@inheritDoc} */ @@ -145,6 +145,18 @@ class UserSession implements IHandleUserSessions return User::isModerator($this->getLocalUserId()); } + /** {@inheritDoc} */ + public function isVisitor(): bool + { + return $this->session->get('authenticated', false) && $this->session->get('visitor_id') && !$this->session->get('uid'); + } + + /** {@inheritDoc} */ + public function isUnauthenticated(): bool + { + return !$this->session->get('authenticated', false); + } + /** {@inheritDoc} */ public function setVisitorsContacts(string $my_url) { diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 9db15b52c4..a619432e43 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -31,21 +31,14 @@ use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\Core\Search; -use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Network\HTTPClient\Client\HttpClientAccept; -use Friendica\Network\HTTPClient\Client\HttpClientOptions; -use Friendica\Network\HTTPClient\Client\HttpClientRequest; use Friendica\Network\HTTPException; -use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Protocol\Activity; use Friendica\Protocol\Diaspora; use Friendica\Security\PermissionSet\Entity\PermissionSet; use Friendica\Util\DateTimeFormat; -use Friendica\Util\HTTPSignature; -use Friendica\Util\Network; use Friendica\Util\Proxy; use Friendica\Util\Strings; @@ -696,218 +689,6 @@ class Profile ]); } - /** - * Process the 'zrl' parameter and initiate the remote authentication. - * - * This method checks if the visitor has a public contact entry and - * redirects the visitor to his/her instance to start the magic auth (Authentication) - * process. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php - * - * The implementation for Friendica sadly differs in some points from the one for Hubzilla: - * - Hubzilla uses the "zid" parameter, while for Friendica it had been replaced with "zrl" - * - There seem to be some reverse authentication (rmagic) that isn't implemented in Friendica at all - * - * It would be favourable to harmonize the two implementations. - * - * @return void - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function zrlInit() - { - $my_url = DI::userSession()->getMyUrl(); - $my_url = Network::isUrlValid($my_url); - - if (empty($my_url) || DI::userSession()->getLocalUserId()) { - return; - } - - $addr = $_GET['addr'] ?? $my_url; - - $arr = ['zrl' => $my_url, 'url' => DI::args()->getCommand()]; - Hook::callAll('zrl_init', $arr); - - // Try to find the public contact entry of the visitor. - $contact = Contact::getByURL($my_url, null, ['id', 'url', 'gsid']); - if (empty($contact)) { - Logger::info('No contact record found', ['url' => $my_url]); - return; - } - - if (DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) { - Logger::info('The visitor is already authenticated', ['url' => $my_url]); - return; - } - - $gserver = DBA::selectFirst('gserver', ['url', 'authredirect'], ['id' => $contact['gsid']]); - if (empty($gserver) || empty($gserver['authredirect'])) { - Logger::info('No server record found or magic path not defined for server', ['id' => $contact['gsid'], 'gserver' => $gserver]); - return; - } - - // Avoid endless loops - $cachekey = 'zrlInit:' . $my_url; - if (DI::cache()->get($cachekey)) { - Logger::info('URL ' . $my_url . ' already tried to authenticate.'); - return; - } else { - DI::cache()->set($cachekey, true, Duration::MINUTE); - } - - Logger::info('Not authenticated. Invoking reverse magic-auth', ['url' => $my_url]); - - // Remove the "addr" parameter from the destination. It is later added as separate parameter again. - $addr_request = 'addr=' . urlencode($addr); - $query = rtrim(str_replace($addr_request, '', DI::args()->getQueryString()), '?&'); - - // The other instance needs to know where to redirect. - $dest = urlencode(DI::baseUrl() . '/' . $query); - - if ($gserver['url'] != DI::baseUrl() && !strstr($dest, '/magic')) { - $magic_path = $gserver['authredirect'] . '?f=&rev=1&owa=1&dest=' . $dest . '&' . $addr_request; - - Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path); - System::externalRedirect($magic_path); - } - } - - /** - * Set the visitor cookies (see remote_user()) for the given handle - * - * @param string $handle Visitor handle - * - * @return array Visitor contact array - */ - public static function addVisitorCookieForHandle(string $handle): array - { - $a = DI::app(); - - // Try to find the public contact entry of the visitor. - $cid = Contact::getIdForURL($handle); - if (!$cid) { - Logger::info('Handle not found', ['handle' => $handle]); - return []; - } - - $visitor = Contact::getById($cid); - - // Authenticate the visitor. - DI::userSession()->setMultiple([ - 'authenticated' => 0, - 'visitor_id' => $visitor['id'], - 'visitor_handle' => $visitor['addr'], - 'visitor_home' => $visitor['url'], - 'my_url' => $visitor['url'], - 'remote_comment' => $visitor['subscribe'], - ]); - - DI::userSession()->setVisitorsContacts($visitor['url']); - - $a->setContactId($visitor['id']); - - Logger::info('Authenticated visitor', ['url' => $visitor['url']]); - - return $visitor; - } - - /** - * Set the visitor cookies (see remote_user()) for signed HTTP requests - * - * @param array $server The content of the $_SERVER superglobal - * @return array Visitor contact array - * @throws InternalServerErrorException - */ - public static function addVisitorCookieForHTTPSigner(array $server): array - { - $requester = HTTPSignature::getSigner('', $server); - if (empty($requester)) { - return []; - } - return Profile::addVisitorCookieForHandle($requester); - } - - /** - * OpenWebAuth authentication. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php - * - * @param string $token - * - * @return void - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function openWebAuthInit(string $token) - { - $a = DI::app(); - - // Clean old OpenWebAuthToken entries. - OpenWebAuthToken::purge('owt', '3 MINUTE'); - - // Check if the token we got is the same one - // we have stored in the database. - $visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token); - - if ($visitor_handle === false) { - return; - } - - $visitor = self::addVisitorCookieForHandle($visitor_handle); - if (empty($visitor)) { - return; - } - - $arr = [ - 'visitor' => $visitor, - 'url' => DI::args()->getQueryString() - ]; - /** - * @hooks magic_auth_success - * Called when a magic-auth was successful. - * * \e array \b visitor - * * \e string \b url - */ - Hook::callAll('magic_auth_success', $arr); - - $a->setContactId($arr['visitor']['id']); - - DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHost(), $visitor['name'])); - - Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']); - } - - /** - * Returns URL with URL-encoded zrl parameter - * - * @param string $url URL to enhance - * @param bool $force Either to force adding zrl parameter - * - * @return string URL with 'zrl' parameter or original URL in case of no Friendica profile URL - */ - public static function zrl(string $url, bool $force = false): string - { - if (!strlen($url)) { - return $url; - } - if (!strpos($url, '/profile/') && !$force) { - return $url; - } - if ($force && substr($url, -1, 1) !== '/') { - $url = $url . '/'; - } - - $achar = strpos($url, '?') ? '&' : '?'; - $mine = DI::userSession()->getMyUrl(); - - if ($mine && !Strings::compareLink($mine, $url)) { - return $url . $achar . 'zrl=' . urlencode($mine); - } - - return $url; - } - /** * Get the user ID of the page owner. * diff --git a/src/Module/Contact/Follow.php b/src/Module/Contact/Follow.php index ece0d0e4ba..ac5f323be9 100644 --- a/src/Module/Contact/Follow.php +++ b/src/Module/Contact/Follow.php @@ -32,12 +32,12 @@ use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\Post; -use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Module\Response; use Friendica\Navigation\SystemMessages; use Friendica\Network\HTTPException\ForbiddenException; use Friendica\Network\Probe; +use Friendica\Security\OpenWebAuth; use Friendica\Util\Profiler; use Friendica\Util\Strings; use GuzzleHttp\Psr7\Uri; @@ -175,7 +175,7 @@ class Follow extends BaseModule '$action' => $requestUrl, '$name' => $contact['name'], '$url' => $contact['url'], - '$zrl' => Profile::zrl($contact['url']), + '$zrl' => OpenWebAuth::getZrlUrl($contact['url']), '$myaddr' => $myaddr, '$keywords' => $contact['keywords'], diff --git a/src/Module/Directory.php b/src/Module/Directory.php index 9d7370df51..8517f2a851 100644 --- a/src/Module/Directory.php +++ b/src/Module/Directory.php @@ -32,6 +32,7 @@ use Friendica\DI; use Friendica\Model; use Friendica\Model\Profile; use Friendica\Network\HTTPException; +use Friendica\Security\OpenWebAuth; /** * Shows the local directory of this node @@ -63,7 +64,7 @@ class Directory extends BaseModule $gDirPath = ''; $dirURL = Search::getGlobalDirectory(); if (strlen($dirURL)) { - $gDirPath = Profile::zrl($dirURL, true); + $gDirPath = OpenWebAuth::getZrlUrl($dirURL, true); } $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 60); diff --git a/src/Module/Photo.php b/src/Module/Photo.php index 4fd995e09d..e73d7e1d91 100644 --- a/src/Module/Photo.php +++ b/src/Module/Photo.php @@ -21,7 +21,6 @@ namespace Friendica\Module; -use Friendica\BaseModule; use Friendica\Contact\Header; use Friendica\Core\Logger; use Friendica\Core\Protocol; @@ -30,7 +29,6 @@ use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Photo as MPhoto; use Friendica\Model\Post; -use Friendica\Model\Profile; use Friendica\Core\Storage\Type\ExternalResource; use Friendica\Core\Storage\Type\SystemResource; use Friendica\Core\System; @@ -42,8 +40,8 @@ use Friendica\Network\HTTPClient\Client\HttpClientRequest; use Friendica\Network\HTTPException; use Friendica\Network\HTTPException\NotModifiedException; use Friendica\Object\Image; +use Friendica\Security\OpenWebAuth; use Friendica\Util\Images; -use Friendica\Util\Network; use Friendica\Util\ParseUrl; use Friendica\Util\Proxy; use Friendica\Worker\UpdateContact; @@ -78,7 +76,7 @@ class Photo extends BaseApi throw new NotModifiedException(); } - Profile::addVisitorCookieForHTTPSigner($this->server); + OpenWebAuth::addVisitorCookieForHTTPSigner($this->server); $customsize = 0; $square_resize = true; diff --git a/src/Module/Register.php b/src/Module/Register.php index 0b488f6cc6..56139be07a 100644 --- a/src/Module/Register.php +++ b/src/Module/Register.php @@ -29,6 +29,7 @@ use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; @@ -51,11 +52,16 @@ class Register extends BaseModule /** @var Tos */ protected $tos; - public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = []) + /** @var IHandleUserSessions */ + private $session; + + public function __construct(IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = []) { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); $this->tos = new Tos($l10n, $baseUrl, $args, $logger, $profiler, $response, $config, $server, $parameters); + + $this->session = $session; } /** @@ -242,7 +248,7 @@ class Register extends BaseModule case self::CLOSED: default: - if (empty($_SESSION['authenticated']) && empty($_SESSION['administrator'])) { + if (!$this->session->isSiteAdmin()) { DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.')); return; } diff --git a/src/Security/Authentication.php b/src/Security/Authentication.php index 93e6344a35..91fe1a0487 100644 --- a/src/Security/Authentication.php +++ b/src/Security/Authentication.php @@ -34,12 +34,12 @@ use Friendica\DI; use Friendica\Model\User; use Friendica\Network\HTTPException; use Friendica\Security\TwoFactor\Repository\TrustedBrowser; -use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use LightOpenID; use Friendica\Core\L10n; use Friendica\Core\Worker; use Friendica\Model\Contact; +use Friendica\Util\Strings; use Psr\Log\LoggerInterface; /** @@ -146,7 +146,7 @@ class Authentication $this->cookie->send(); // Do the authentication if not done by now - if (!$this->session->get('authenticated')) { + if (!$this->session->isAuthenticated()) { $this->setForUser($a, $user); if ($this->config->get('system', 'paranoia')) { @@ -156,46 +156,44 @@ class Authentication } } - if ($this->session->get('authenticated')) { - if ($this->session->get('visitor_id') && !$this->session->get('uid')) { - $contact = $this->dba->selectFirst('contact', ['id'], ['id' => $this->session->get('visitor_id')]); - if ($this->dba->isResult($contact)) { - $a->setContactId($contact['id']); - } + if ($this->session->isVisitor()) { + $contact = $this->dba->selectFirst('contact', ['id'], ['id' => $this->session->get('visitor_id')]); + if ($this->dba->isResult($contact)) { + $a->setContactId($contact['id']); } + } - if ($this->session->get('uid')) { - // already logged in user returning - $check = $this->config->get('system', 'paranoia'); - // extra paranoia - if the IP changed, log them out - if ($check && ($this->session->get('addr') != $this->remoteAddress)) { - $this->logger->notice('Session address changed. Paranoid setting in effect, blocking session. ', [ - 'addr' => $this->session->get('addr'), - 'remote_addr' => $this->remoteAddress - ] - ); - $this->session->clear(); - $this->baseUrl->redirect(); - } - - $user = $this->dba->selectFirst( - 'user', - [], - [ - 'uid' => $this->session->get('uid'), - 'blocked' => false, - 'account_expired' => false, - 'account_removed' => false, - 'verified' => true, - ] + if ($this->session->isAuthenticated()) { + // already logged in user returning + $check = $this->config->get('system', 'paranoia'); + // extra paranoia - if the IP changed, log them out + if ($check && ($this->session->get('addr') != $this->remoteAddress)) { + $this->logger->notice('Session address changed. Paranoid setting in effect, blocking session. ', [ + 'addr' => $this->session->get('addr'), + 'remote_addr' => $this->remoteAddress + ] ); - if (!$this->dba->isResult($user)) { - $this->session->clear(); - $this->baseUrl->redirect(); - } - - $this->setForUser($a, $user); + $this->session->clear(); + $this->baseUrl->redirect(); } + + $user = $this->dba->selectFirst( + 'user', + [], + [ + 'uid' => $this->session->get('uid'), + 'blocked' => false, + 'account_expired' => false, + 'account_removed' => false, + 'verified' => true, + ] + ); + if (!$this->dba->isResult($user)) { + $this->session->clear(); + $this->baseUrl->redirect(); + } + + $this->setForUser($a, $user); } } @@ -446,4 +444,25 @@ class Authentication $this->baseUrl->redirect('2fa'); } } + + /** + * Set the URL of an unauthenticated visitor + * + * @param string $url + * @return void + */ + public function setUnauthenticatedVisitor(string $url) + { + if (Strings::compareLink($this->session->get('visitor_home'), $url)) { + return; + } + + $this->session->set('my_url', $url); + $this->session->set('authenticated', 0); + + $remote_contact = Contact::getByURL($url, false, ['subscribe']); + if (!empty($remote_contact['subscribe'])) { + $this->session->set('remote_comment', $remote_contact['subscribe']); + } + } } diff --git a/src/Security/OpenWebAuth.php b/src/Security/OpenWebAuth.php new file mode 100644 index 0000000000..2c31f322df --- /dev/null +++ b/src/Security/OpenWebAuth.php @@ -0,0 +1,252 @@ +. + * + */ + +namespace Friendica\Security; + +use Friendica\Core\Cache\Enum\Duration; +use Friendica\Core\Hook; +use Friendica\Core\Logger; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\OpenWebAuthToken; +use Friendica\Util\HTTPSignature; +use Friendica\Util\Network; +use Friendica\Util\Strings; + +/** + * Authentication via OpenWebAuth + */ +class OpenWebAuth +{ + /** + * Process the 'zrl' parameter and initiate the remote authentication. + * + * This method checks if the visitor has a public contact entry and + * redirects the visitor to his/her instance to start the magic auth (Authentication) + * process. + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php + * + * The implementation for Friendica sadly differs in some points from the one for Hubzilla: + * - Hubzilla uses the "zid" parameter, while for Friendica it had been replaced with "zrl" + * - There seem to be some reverse authentication (rmagic) that isn't implemented in Friendica at all + * + * It would be favourable to harmonize the two implementations. + * + * @return void + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function zrlInit() + { + $my_url = DI::userSession()->getMyUrl(); + $my_url = Network::isUrlValid($my_url); + + if (empty($my_url) || DI::userSession()->getLocalUserId()) { + return; + } + + $addr = $_GET['addr'] ?? $my_url; + + $arr = ['zrl' => $my_url, 'url' => DI::args()->getCommand()]; + Hook::callAll('zrl_init', $arr); + + // Try to find the public contact entry of the visitor. + $contact = Contact::getByURL($my_url, null, ['id', 'url', 'gsid']); + if (empty($contact)) { + Logger::info('No contact record found', ['url' => $my_url]); + return; + } + + if (DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) { + Logger::info('The visitor is already authenticated', ['url' => $my_url]); + return; + } + + $gserver = DBA::selectFirst('gserver', ['url', 'authredirect'], ['id' => $contact['gsid']]); + if (empty($gserver) || empty($gserver['authredirect'])) { + Logger::info('No server record found or magic path not defined for server', ['id' => $contact['gsid'], 'gserver' => $gserver]); + return; + } + + // Avoid endless loops + $cachekey = 'zrlInit:' . $my_url; + if (DI::cache()->get($cachekey)) { + Logger::info('URL ' . $my_url . ' already tried to authenticate.'); + return; + } else { + DI::cache()->set($cachekey, true, Duration::MINUTE); + } + + Logger::info('Not authenticated. Invoking reverse magic-auth', ['url' => $my_url]); + + // Remove the "addr" parameter from the destination. It is later added as separate parameter again. + $addr_request = 'addr=' . urlencode($addr); + $query = rtrim(str_replace($addr_request, '', DI::args()->getQueryString()), '?&'); + + // The other instance needs to know where to redirect. + $dest = urlencode(DI::baseUrl() . '/' . $query); + + if ($gserver['url'] != DI::baseUrl() && !strstr($dest, '/magic')) { + $magic_path = $gserver['authredirect'] . '?f=&rev=1&owa=1&dest=' . $dest . '&' . $addr_request; + + Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path); + System::externalRedirect($magic_path); + } + } + + /** + * OpenWebAuth authentication. + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php + * + * @param string $token + * + * @return void + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function init(string $token) + { + $a = DI::app(); + + // Clean old OpenWebAuthToken entries. + OpenWebAuthToken::purge('owt', '3 MINUTE'); + + // Check if the token we got is the same one + // we have stored in the database. + $visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token); + + if ($visitor_handle === false) { + return; + } + + $visitor = self::addVisitorCookieForHandle($visitor_handle); + if (empty($visitor)) { + return; + } + + $arr = [ + 'visitor' => $visitor, + 'url' => DI::args()->getQueryString() + ]; + /** + * @hooks magic_auth_success + * Called when a magic-auth was successful. + * * \e array \b visitor + * * \e string \b url + */ + Hook::callAll('magic_auth_success', $arr); + + $a->setContactId($arr['visitor']['id']); + + DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHost(), $visitor['name'])); + + Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']); + } + + /** + * Set the visitor cookies (see remote_user()) for the given handle + * + * @param string $handle Visitor handle + * + * @return array Visitor contact array + */ + public static function addVisitorCookieForHandle(string $handle): array + { + $a = DI::app(); + + // Try to find the public contact entry of the visitor. + $cid = Contact::getIdForURL($handle); + if (!$cid) { + Logger::info('Handle not found', ['handle' => $handle]); + return []; + } + + $visitor = Contact::getById($cid); + + // Authenticate the visitor. + DI::userSession()->setMultiple([ + 'authenticated' => 1, + 'visitor_id' => $visitor['id'], + 'visitor_handle' => $visitor['addr'], + 'visitor_home' => $visitor['url'], + 'my_url' => $visitor['url'], + 'remote_comment' => $visitor['subscribe'], + ]); + + DI::userSession()->setVisitorsContacts($visitor['url']); + + $a->setContactId($visitor['id']); + + Logger::info('Authenticated visitor', ['url' => $visitor['url']]); + + return $visitor; + } + + /** + * Set the visitor cookies (see remote_user()) for signed HTTP requests + * + * @param array $server The content of the $_SERVER superglobal + * @return array Visitor contact array + * @throws InternalServerErrorException + */ + public static function addVisitorCookieForHTTPSigner(array $server): array + { + $requester = HTTPSignature::getSigner('', $server); + if (empty($requester)) { + return []; + } + return self::addVisitorCookieForHandle($requester); + } + + /** + * Returns URL with URL-encoded zrl parameter + * + * @param string $url URL to enhance + * @param bool $force Either to force adding zrl parameter + * + * @return string URL with 'zrl' parameter or original URL in case of no Friendica profile URL + */ + public static function getZrlUrl(string $url, bool $force = false): string + { + if (!strlen($url)) { + return $url; + } + if (!strpos($url, '/profile/') && !$force) { + return $url; + } + if ($force && substr($url, -1, 1) !== '/') { + $url = $url . '/'; + } + + $achar = strpos($url, '?') ? '&' : '?'; + $mine = DI::userSession()->getMyUrl(); + + if ($mine && !Strings::compareLink($mine, $url)) { + return $url . $achar . 'zrl=' . urlencode($mine); + } + + return $url; + } +} diff --git a/tests/src/Core/Session/UserSessionTest.php b/tests/src/Core/Session/UserSessionTest.php index 291cd0fd59..7721dbfddd 100644 --- a/tests/src/Core/Session/UserSessionTest.php +++ b/tests/src/Core/Session/UserSessionTest.php @@ -190,6 +190,7 @@ class UserSessionTest extends MockedTest 'authenticated' => [ 'data' => [ 'authenticated' => true, + 'uid' => 21, ], 'expected' => true, ], @@ -199,6 +200,13 @@ class UserSessionTest extends MockedTest ], 'expected' => false, ], + 'remote_visitor' => [ + 'data' => [ + 'authenticated' => true, + 'visitor_id' => 21, + ], + 'expected' => false, + ], 'missing' => [ 'data' => [ ], @@ -215,4 +223,104 @@ class UserSessionTest extends MockedTest $userSession = new UserSession(new ArraySession($data)); $this->assertEquals($expected, $userSession->isAuthenticated()); } + + public function dataIsVisitor() + { + return [ + 'local_user' => [ + 'data' => [ + 'authenticated' => true, + 'uid' => 21, + ], + 'expected' => false, + ], + 'not_authenticated' => [ + 'data' => [ + 'authenticated' => false, + ], + 'expected' => false, + ], + 'remote_visitor' => [ + 'data' => [ + 'authenticated' => true, + 'visitor_id' => 21, + ], + 'expected' => true, + ], + 'remote_unauthenticated_visitor' => [ + 'data' => [ + 'authenticated' => false, + 'visitor_id' => 21, + ], + 'expected' => false, + ], + 'missing' => [ + 'data' => [ + ], + 'expected' => false, + ], + ]; + } + + /** + * @dataProvider dataIsVisitor + */ + public function testIsVisitor(array $data, $expected) + { + $userSession = new UserSession(new ArraySession($data)); + $this->assertEquals($expected, $userSession->isVisitor()); + } + + public function dataIsUnauthenticated() + { + return [ + 'local_user' => [ + 'data' => [ + 'authenticated' => true, + 'uid' => 21, + ], + 'expected' => false, + ], + 'not_authenticated' => [ + 'data' => [ + 'authenticated' => false, + ], + 'expected' => true, + ], + 'authenticated' => [ + 'data' => [ + 'authenticated' => true, + ], + 'expected' => false, + ], + 'remote_visitor' => [ + 'data' => [ + 'authenticated' => true, + 'visitor_id' => 21, + ], + 'expected' => false, + ], + 'remote_unauthenticated_visitor' => [ + 'data' => [ + 'authenticated' => false, + 'visitor_id' => 21, + ], + 'expected' => true, + ], + 'missing' => [ + 'data' => [ + ], + 'expected' => true, + ], + ]; + } + + /** + * @dataProvider dataIsUnauthenticated + */ + public function testIsUnauthenticated(array $data, $expected) + { + $userSession = new UserSession(new ArraySession($data)); + $this->assertEquals($expected, $userSession->isUnauthenticated()); + } } diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 557f7bbed0..9dc43f561b 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2024.06-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-26 15:56+0000\n" +"POT-Creation-Date: 2024-05-27 04:49+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,8 +63,8 @@ msgstr "" #: src/Module/Post/Edit.php:76 src/Module/Profile/Common.php:75 #: src/Module/Profile/Contacts.php:78 src/Module/Profile/Photos.php:92 #: src/Module/Profile/Schedule.php:39 src/Module/Profile/Schedule.php:56 -#: src/Module/Register.php:78 src/Module/Register.php:91 -#: src/Module/Register.php:207 src/Module/Register.php:246 +#: src/Module/Register.php:84 src/Module/Register.php:97 +#: src/Module/Register.php:213 src/Module/Register.php:252 #: src/Module/Search/Directory.php:37 src/Module/Settings/Account.php:50 #: src/Module/Settings/Account.php:386 src/Module/Settings/Channels.php:66 #: src/Module/Settings/Channels.php:141 src/Module/Settings/Delegation.php:90 @@ -388,14 +388,14 @@ msgid "Save" msgstr "" #: mod/photos.php:66 mod/photos.php:129 mod/photos.php:573 -#: src/Model/Event.php:512 src/Model/Profile.php:234 +#: src/Model/Event.php:512 src/Model/Profile.php:227 #: src/Module/Calendar/Export.php:74 src/Module/Calendar/Show.php:74 #: src/Module/DFRN/Poll.php:43 src/Module/Feed.php:64 src/Module/HCard.php:51 #: src/Module/Profile/Common.php:62 src/Module/Profile/Common.php:71 #: src/Module/Profile/Contacts.php:64 src/Module/Profile/Contacts.php:72 #: src/Module/Profile/Conversations.php:91 src/Module/Profile/Media.php:56 #: src/Module/Profile/Photos.php:83 src/Module/Profile/RemoteFollow.php:71 -#: src/Module/Register.php:268 +#: src/Module/Register.php:274 msgid "User not found." msgstr "" @@ -449,7 +449,7 @@ msgid "%1$s was tagged in %2$s by %3$s" msgstr "" #: mod/photos.php:577 src/Module/Conversation/Community.php:160 -#: src/Module/Directory.php:48 src/Module/Profile/Photos.php:293 +#: src/Module/Directory.php:49 src/Module/Profile/Photos.php:293 #: src/Module/Search/Index.php:65 msgid "Public access denied." msgstr "" @@ -656,11 +656,11 @@ msgstr "" msgid "Map" msgstr "" -#: src/App.php:438 +#: src/App.php:441 msgid "No system theme config value set." msgstr "" -#: src/App.php:546 +#: src/App.php:549 msgid "Apologies but the website is unavailable at the moment." msgstr "" @@ -1384,7 +1384,7 @@ msgid "Public post" msgstr "" #: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:131 -#: src/Model/Profile.php:483 src/Module/Admin/Logs/View.php:94 +#: src/Model/Profile.php:476 src/Module/Admin/Logs/View.php:94 #: src/Module/Post/Edit.php:181 msgid "Message" msgstr "" @@ -1891,7 +1891,7 @@ msgstr "" #: src/Content/Item.php:430 src/Content/Item.php:453 src/Model/Contact.php:1168 #: src/Model/Contact.php:1224 src/Model/Contact.php:1234 -#: src/Module/Directory.php:157 src/Module/Settings/Profile/Index.php:259 +#: src/Module/Directory.php:158 src/Module/Settings/Profile/Index.php:259 msgid "View Profile" msgstr "" @@ -1900,7 +1900,7 @@ msgid "View Photos" msgstr "" #: src/Content/Item.php:432 src/Model/Contact.php:1202 -#: src/Model/Profile.php:468 +#: src/Model/Profile.php:461 msgid "Network Posts" msgstr "" @@ -2058,7 +2058,7 @@ msgstr "" msgid "Home Page" msgstr "" -#: src/Content/Nav.php:255 src/Module/Register.php:169 +#: src/Content/Nav.php:255 src/Module/Register.php:175 #: src/Module/Security/Login.php:124 msgid "Register" msgstr "" @@ -2138,7 +2138,7 @@ msgid "Information about this friendica instance" msgstr "" #: src/Content/Nav.php:301 src/Module/Admin/Tos.php:78 -#: src/Module/BaseAdmin.php:95 src/Module/Register.php:177 +#: src/Module/BaseAdmin.php:95 src/Module/Register.php:183 #: src/Module/Tos.php:101 msgid "Terms of Service" msgstr "" @@ -2313,7 +2313,7 @@ msgid "The end" msgstr "" #: src/Content/Text/HTML.php:861 src/Content/Widget/VCard.php:127 -#: src/Model/Profile.php:477 src/Module/Contact/Profile.php:478 +#: src/Model/Profile.php:470 src/Module/Contact/Profile.php:478 msgid "Follow" msgstr "" @@ -2353,7 +2353,7 @@ msgid "Examples: Robert Morgenstein, Fishing" msgstr "" #: src/Content/Widget.php:82 src/Module/Contact.php:460 -#: src/Module/Directory.php:96 view/theme/vier/theme.php:197 +#: src/Module/Directory.php:97 view/theme/vier/theme.php:197 msgid "Find" msgstr "" @@ -2374,7 +2374,7 @@ msgstr "" msgid "Invite Friends" msgstr "" -#: src/Content/Widget.php:87 src/Module/Directory.php:88 +#: src/Content/Widget.php:87 src/Module/Directory.php:89 #: view/theme/vier/theme.php:202 msgid "Global Directory" msgstr "" @@ -2486,46 +2486,46 @@ msgid "More Trending Tags" msgstr "" #: src/Content/Widget/VCard.php:105 src/Model/Contact.php:1196 -#: src/Model/Profile.php:462 +#: src/Model/Profile.php:455 msgid "Post to group" msgstr "" #: src/Content/Widget/VCard.php:110 src/Model/Contact.php:1200 -#: src/Model/Profile.php:466 src/Module/Moderation/Item/Source.php:85 +#: src/Model/Profile.php:459 src/Module/Moderation/Item/Source.php:85 msgid "Mention" msgstr "" -#: src/Content/Widget/VCard.php:120 src/Model/Profile.php:381 +#: src/Content/Widget/VCard.php:120 src/Model/Profile.php:374 #: src/Module/Contact/Profile.php:414 src/Module/Profile/Profile.php:199 msgid "XMPP:" msgstr "" -#: src/Content/Widget/VCard.php:121 src/Model/Profile.php:382 +#: src/Content/Widget/VCard.php:121 src/Model/Profile.php:375 #: src/Module/Contact/Profile.php:416 src/Module/Profile/Profile.php:203 msgid "Matrix:" msgstr "" #: src/Content/Widget/VCard.php:122 src/Model/Event.php:82 #: src/Model/Event.php:109 src/Model/Event.php:471 src/Model/Event.php:960 -#: src/Model/Profile.php:376 src/Module/Contact/Profile.php:412 -#: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 +#: src/Model/Profile.php:369 src/Module/Contact/Profile.php:412 +#: src/Module/Directory.php:148 src/Module/Notifications/Introductions.php:187 #: src/Module/Profile/Profile.php:221 msgid "Location:" msgstr "" -#: src/Content/Widget/VCard.php:125 src/Model/Profile.php:490 +#: src/Content/Widget/VCard.php:125 src/Model/Profile.php:483 #: src/Module/Notifications/Introductions.php:201 msgid "Network:" msgstr "" #: src/Content/Widget/VCard.php:129 src/Model/Contact.php:1228 -#: src/Model/Contact.php:1240 src/Model/Profile.php:479 +#: src/Model/Contact.php:1240 src/Model/Profile.php:472 #: src/Module/Contact/Profile.php:470 msgid "Unfollow" msgstr "" #: src/Content/Widget/VCard.php:135 src/Model/Contact.php:1198 -#: src/Model/Profile.php:464 +#: src/Model/Profile.php:457 msgid "View group" msgstr "" @@ -3581,149 +3581,144 @@ msgstr "" msgid "Wall Photos" msgstr "" -#: src/Model/Profile.php:364 src/Module/Profile/Profile.php:283 +#: src/Model/Profile.php:357 src/Module/Profile/Profile.php:283 #: src/Module/Profile/Profile.php:285 msgid "Edit profile" msgstr "" -#: src/Model/Profile.php:366 +#: src/Model/Profile.php:359 msgid "Change profile photo" msgstr "" -#: src/Model/Profile.php:379 src/Module/Directory.php:152 +#: src/Model/Profile.php:372 src/Module/Directory.php:153 #: src/Module/Profile/Profile.php:209 msgid "Homepage:" msgstr "" -#: src/Model/Profile.php:380 src/Module/Contact/Profile.php:418 +#: src/Model/Profile.php:373 src/Module/Contact/Profile.php:418 #: src/Module/Notifications/Introductions.php:189 msgid "About:" msgstr "" -#: src/Model/Profile.php:481 +#: src/Model/Profile.php:474 msgid "Atom feed" msgstr "" -#: src/Model/Profile.php:488 +#: src/Model/Profile.php:481 msgid "This website has been verified to belong to the same person." msgstr "" -#: src/Model/Profile.php:539 +#: src/Model/Profile.php:532 msgid "F d" msgstr "" -#: src/Model/Profile.php:603 src/Model/Profile.php:680 +#: src/Model/Profile.php:596 src/Model/Profile.php:673 msgid "[today]" msgstr "" -#: src/Model/Profile.php:612 +#: src/Model/Profile.php:605 msgid "Birthday Reminders" msgstr "" -#: src/Model/Profile.php:613 +#: src/Model/Profile.php:606 msgid "Birthdays this week:" msgstr "" -#: src/Model/Profile.php:629 +#: src/Model/Profile.php:622 msgid "g A l F d" msgstr "" -#: src/Model/Profile.php:667 +#: src/Model/Profile.php:660 msgid "[No description]" msgstr "" -#: src/Model/Profile.php:693 +#: src/Model/Profile.php:686 msgid "Event Reminders" msgstr "" -#: src/Model/Profile.php:694 +#: src/Model/Profile.php:687 msgid "Upcoming events the next 7 days:" msgstr "" -#: src/Model/Profile.php:876 -#, php-format -msgid "OpenWebAuth: %1$s welcomes %2$s" -msgstr "" - -#: src/Model/Profile.php:1016 +#: src/Model/Profile.php:797 msgid "Hometown:" msgstr "" -#: src/Model/Profile.php:1017 +#: src/Model/Profile.php:798 msgid "Marital Status:" msgstr "" -#: src/Model/Profile.php:1018 +#: src/Model/Profile.php:799 msgid "With:" msgstr "" -#: src/Model/Profile.php:1019 +#: src/Model/Profile.php:800 msgid "Since:" msgstr "" -#: src/Model/Profile.php:1020 +#: src/Model/Profile.php:801 msgid "Sexual Preference:" msgstr "" -#: src/Model/Profile.php:1021 +#: src/Model/Profile.php:802 msgid "Political Views:" msgstr "" -#: src/Model/Profile.php:1022 +#: src/Model/Profile.php:803 msgid "Religious Views:" msgstr "" -#: src/Model/Profile.php:1023 +#: src/Model/Profile.php:804 msgid "Likes:" msgstr "" -#: src/Model/Profile.php:1024 +#: src/Model/Profile.php:805 msgid "Dislikes:" msgstr "" -#: src/Model/Profile.php:1025 +#: src/Model/Profile.php:806 msgid "Title/Description:" msgstr "" -#: src/Model/Profile.php:1026 src/Module/Admin/Summary.php:197 +#: src/Model/Profile.php:807 src/Module/Admin/Summary.php:197 #: src/Module/Moderation/Report/Create.php:280 #: src/Module/Moderation/Summary.php:76 msgid "Summary" msgstr "" -#: src/Model/Profile.php:1027 +#: src/Model/Profile.php:808 msgid "Musical interests" msgstr "" -#: src/Model/Profile.php:1028 +#: src/Model/Profile.php:809 msgid "Books, literature" msgstr "" -#: src/Model/Profile.php:1029 +#: src/Model/Profile.php:810 msgid "Television" msgstr "" -#: src/Model/Profile.php:1030 +#: src/Model/Profile.php:811 msgid "Film/dance/culture/entertainment" msgstr "" -#: src/Model/Profile.php:1031 +#: src/Model/Profile.php:812 msgid "Hobbies/Interests" msgstr "" -#: src/Model/Profile.php:1032 +#: src/Model/Profile.php:813 msgid "Love/romance" msgstr "" -#: src/Model/Profile.php:1033 +#: src/Model/Profile.php:814 msgid "Work/employment" msgstr "" -#: src/Model/Profile.php:1034 +#: src/Model/Profile.php:815 msgid "School/education" msgstr "" -#: src/Model/Profile.php:1035 +#: src/Model/Profile.php:816 msgid "Contact information and Social Networks" msgstr "" @@ -3777,13 +3772,13 @@ msgstr "" msgid "Invalid OpenID url" msgstr "" -#: src/Model/User.php:1218 src/Security/Authentication.php:230 +#: src/Model/User.php:1218 src/Security/Authentication.php:228 msgid "" "We encountered a problem while logging in with the OpenID you provided. " "Please check the correct spelling of the ID." msgstr "" -#: src/Model/User.php:1218 src/Security/Authentication.php:230 +#: src/Model/User.php:1218 src/Security/Authentication.php:228 msgid "The error message was:" msgstr "" @@ -4135,14 +4130,14 @@ msgstr "" #: src/Module/Admin/Features.php:67 #: src/Module/Notifications/Introductions.php:144 -#: src/Module/OAuth/Acknowledge.php:55 src/Module/Register.php:132 +#: src/Module/OAuth/Acknowledge.php:55 src/Module/Register.php:138 #: src/Module/Settings/TwoFactor/Trusted.php:129 msgid "No" msgstr "" #: src/Module/Admin/Features.php:67 src/Module/Contact/Revoke.php:108 #: src/Module/Notifications/Introductions.php:144 -#: src/Module/OAuth/Acknowledge.php:54 src/Module/Register.php:131 +#: src/Module/OAuth/Acknowledge.php:54 src/Module/Register.php:137 #: src/Module/Settings/TwoFactor/Trusted.php:129 msgid "Yes" msgstr "" @@ -4516,7 +4511,7 @@ msgstr "" msgid "Republish users to directory" msgstr "" -#: src/Module/Admin/Site.php:460 src/Module/Register.php:153 +#: src/Module/Admin/Site.php:460 src/Module/Register.php:159 msgid "Registration" msgstr "" @@ -6242,7 +6237,7 @@ msgstr "" #: src/Module/Moderation/Blocklist/Server/Index.php:87 #: src/Module/Moderation/Blocklist/Server/Index.php:115 #: src/Module/Moderation/Blocklist/Server/Index.php:116 -#: src/Module/Moderation/Item/Delete.php:67 src/Module/Register.php:149 +#: src/Module/Moderation/Item/Delete.php:67 src/Module/Register.php:155 #: src/Module/Security/TwoFactor/Verify.php:101 #: src/Module/Settings/Channels.php:190 src/Module/Settings/Channels.php:211 #: src/Module/Settings/TwoFactor/Index.php:161 @@ -7432,19 +7427,19 @@ msgstr "" msgid "Lookup address:" msgstr "" -#: src/Module/Directory.php:74 +#: src/Module/Directory.php:75 msgid "No entries (some entries may be hidden)." msgstr "" -#: src/Module/Directory.php:90 +#: src/Module/Directory.php:91 msgid "Find on this site" msgstr "" -#: src/Module/Directory.php:92 +#: src/Module/Directory.php:93 msgid "Results for:" msgstr "" -#: src/Module/Directory.php:94 +#: src/Module/Directory.php:95 msgid "Site Directory" msgstr "" @@ -9013,21 +9008,21 @@ msgstr "" msgid "ignored" msgstr "" -#: src/Module/Photo.php:124 +#: src/Module/Photo.php:122 msgid "The Photo is not available." msgstr "" -#: src/Module/Photo.php:149 +#: src/Module/Photo.php:147 #, php-format msgid "The Photo with id %s is not available." msgstr "" -#: src/Module/Photo.php:190 +#: src/Module/Photo.php:188 #, php-format msgid "Invalid external resource with url %s." msgstr "" -#: src/Module/Photo.php:192 +#: src/Module/Photo.php:190 #, php-format msgid "Invalid photo with id %s." msgstr "" @@ -9301,170 +9296,170 @@ msgstr "" msgid "Remove post" msgstr "" -#: src/Module/Register.php:85 +#: src/Module/Register.php:91 msgid "Only parent users can create additional accounts." msgstr "" -#: src/Module/Register.php:100 src/Module/User/Import.php:112 +#: src/Module/Register.php:106 src/Module/User/Import.php:112 msgid "" "This site has exceeded the number of allowed daily account registrations. " "Please try again tomorrow." msgstr "" -#: src/Module/Register.php:117 +#: src/Module/Register.php:123 msgid "" "You may (optionally) fill in this form via OpenID by supplying your OpenID " "and clicking \"Register\"." msgstr "" -#: src/Module/Register.php:118 +#: src/Module/Register.php:124 msgid "" "If you are not familiar with OpenID, please leave that field blank and fill " "in the rest of the items." msgstr "" -#: src/Module/Register.php:119 +#: src/Module/Register.php:125 msgid "Your OpenID (optional): " msgstr "" -#: src/Module/Register.php:128 +#: src/Module/Register.php:134 msgid "Include your profile in member directory?" msgstr "" -#: src/Module/Register.php:149 +#: src/Module/Register.php:155 msgid "Note for the admin" msgstr "" -#: src/Module/Register.php:149 +#: src/Module/Register.php:155 msgid "Leave a message for the admin, why you want to join this node" msgstr "" -#: src/Module/Register.php:150 +#: src/Module/Register.php:156 msgid "Membership on this site is by invitation only." msgstr "" -#: src/Module/Register.php:151 +#: src/Module/Register.php:157 msgid "Your invitation code: " msgstr "" -#: src/Module/Register.php:159 +#: src/Module/Register.php:165 msgid "Your Display Name (as you would like it to be displayed on this system" msgstr "" -#: src/Module/Register.php:160 +#: src/Module/Register.php:166 msgid "" "Your Email Address: (Initial information will be send there, so this has to " "be an existing address.)" msgstr "" -#: src/Module/Register.php:161 +#: src/Module/Register.php:167 msgid "Please repeat your e-mail address:" msgstr "" -#: src/Module/Register.php:163 src/Module/Security/PasswordTooLong.php:100 +#: src/Module/Register.php:169 src/Module/Security/PasswordTooLong.php:100 #: src/Module/Settings/Account.php:564 msgid "New Password:" msgstr "" -#: src/Module/Register.php:163 +#: src/Module/Register.php:169 msgid "Leave empty for an auto generated password." msgstr "" -#: src/Module/Register.php:164 src/Module/Security/PasswordTooLong.php:101 +#: src/Module/Register.php:170 src/Module/Security/PasswordTooLong.php:101 #: src/Module/Settings/Account.php:565 msgid "Confirm:" msgstr "" -#: src/Module/Register.php:165 +#: src/Module/Register.php:171 #, php-format msgid "" "Choose a profile nickname. This must begin with a text character. Your " "profile address on this site will then be \"nickname@%s\"." msgstr "" -#: src/Module/Register.php:166 +#: src/Module/Register.php:172 msgid "Choose a nickname: " msgstr "" -#: src/Module/Register.php:174 src/Module/User/Import.php:118 +#: src/Module/Register.php:180 src/Module/User/Import.php:118 msgid "Import" msgstr "" -#: src/Module/Register.php:175 +#: src/Module/Register.php:181 msgid "Import your profile to this friendica instance" msgstr "" -#: src/Module/Register.php:182 +#: src/Module/Register.php:188 msgid "Note: This node explicitly contains adult content" msgstr "" -#: src/Module/Register.php:184 src/Module/Settings/Delegation.php:181 +#: src/Module/Register.php:190 src/Module/Settings/Delegation.php:181 msgid "Parent Password:" msgstr "" -#: src/Module/Register.php:184 src/Module/Settings/Delegation.php:181 +#: src/Module/Register.php:190 src/Module/Settings/Delegation.php:181 msgid "" "Please enter the password of the parent account to legitimize your request." msgstr "" -#: src/Module/Register.php:213 +#: src/Module/Register.php:219 msgid "Password doesn't match." msgstr "" -#: src/Module/Register.php:219 +#: src/Module/Register.php:225 msgid "Please enter your password." msgstr "" -#: src/Module/Register.php:261 +#: src/Module/Register.php:267 msgid "You have entered too much information." msgstr "" -#: src/Module/Register.php:284 +#: src/Module/Register.php:290 msgid "Please enter the identical mail address in the second field." msgstr "" -#: src/Module/Register.php:292 +#: src/Module/Register.php:298 msgid "Nickname cannot start with a digit." msgstr "" -#: src/Module/Register.php:294 +#: src/Module/Register.php:300 msgid "Nickname can only contain US-ASCII characters." msgstr "" -#: src/Module/Register.php:323 +#: src/Module/Register.php:329 msgid "The additional account was created." msgstr "" -#: src/Module/Register.php:348 +#: src/Module/Register.php:354 msgid "" "Registration successful. Please check your email for further instructions." msgstr "" -#: src/Module/Register.php:355 +#: src/Module/Register.php:361 #, php-format msgid "" "Failed to send email message. Here your accout details:
login: %s
" "password: %s

You can change your password after login." msgstr "" -#: src/Module/Register.php:361 +#: src/Module/Register.php:367 msgid "Registration successful." msgstr "" -#: src/Module/Register.php:370 src/Module/Register.php:377 -#: src/Module/Register.php:387 +#: src/Module/Register.php:376 src/Module/Register.php:383 +#: src/Module/Register.php:393 msgid "Your registration can not be processed." msgstr "" -#: src/Module/Register.php:376 +#: src/Module/Register.php:382 msgid "You have to leave a request note for the admin." msgstr "" -#: src/Module/Register.php:386 +#: src/Module/Register.php:392 msgid "An internal error occured." msgstr "" -#: src/Module/Register.php:408 +#: src/Module/Register.php:414 msgid "Your registration is pending approval by the site owner." msgstr "" @@ -12588,23 +12583,28 @@ msgstr "" msgid "The folder %s must be writable by webserver." msgstr "" -#: src/Security/Authentication.php:216 +#: src/Security/Authentication.php:214 msgid "Login failed." msgstr "" -#: src/Security/Authentication.php:261 +#: src/Security/Authentication.php:259 msgid "Login failed. Please check your credentials." msgstr "" -#: src/Security/Authentication.php:375 +#: src/Security/Authentication.php:373 #, php-format msgid "Welcome %s" msgstr "" -#: src/Security/Authentication.php:376 +#: src/Security/Authentication.php:374 msgid "Please upload a profile photo." msgstr "" +#: src/Security/OpenWebAuth.php:163 +#, php-format +msgid "OpenWebAuth: %1$s welcomes %2$s" +msgstr "" + #: src/Util/EMailer/MailBuilder.php:260 msgid "Friendica Notification" msgstr ""