]> git.mxchange.org Git - friendica.git/commitdiff
OpenWebAuth moved to a separate class / Improved authentication handling
authorMichael <heluecht@pirati.ca>
Mon, 27 May 2024 04:33:28 +0000 (04:33 +0000)
committerMichael <heluecht@pirati.ca>
Tue, 28 May 2024 21:59:52 +0000 (21:59 +0000)
14 files changed:
src/App.php
src/Content/Nav.php
src/Content/Widget.php
src/Core/Session/Capability/IHandleUserSessions.php
src/Core/Session/Model/UserSession.php
src/Model/Profile.php
src/Module/Contact/Follow.php
src/Module/Directory.php
src/Module/Photo.php
src/Module/Register.php
src/Security/Authentication.php
src/Security/OpenWebAuth.php [new file with mode: 0644]
tests/src/Core/Session/UserSessionTest.php
view/lang/C/messages.po

index 96b8d8b947a5d32f9c4ab7c9b4f8a0af186bf0ec..6673ec791493e1d1066be300ff94cfd7cc7cbbee 100644 (file)
@@ -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');
                        }
 
index 3828a4954c63399e895c605a8c27dc1bdddbe84b..aa8c3f83b93136e6ec9197f7e06e471633a16442 100644 (file)
@@ -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) &&
index 8550e44a5eab11fb5ae5fb03e2e6112384e81fcd..1108af51240dde55604d51fe1c3aa5c2436f1033 100644 (file)
@@ -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 = [];
index fc7e06b783df82241c381b3dbad1c06d0dcf8801..83ebad502b6926fa983848ad53a0bce8768c7257 100644 (file)
@@ -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
         *
index 0b1a197e70129356ce629e10f28bf1abbfd6c83a..4108c791773c33463890b11b5c7995214c1a8034 100644 (file)
@@ -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)
        {
index 9db15b52c4b575ce0bd437152fbbdeb583332c07..a619432e43ba9956bc4ea91e208de7e8369a4a70 100644 (file)
@@ -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.
         *
index ece0d0e4ba2b349d9a7be69c0fd897a5c572ed35..ac5f323be99b35e386380386a35768628636a41c 100644 (file)
@@ -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'],
 
index 9d7370df51dd4718805931e2008214028cfa98c0..8517f2a851c1bf449213c920c7b340349cba28f5 100644 (file)
@@ -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);
index 4fd995e09d64c35f2471c598c7652d36882d548c..e73d7e1d91193846f183f4475be56dfd3c23096f 100644 (file)
@@ -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;
index 0b488f6cc61d9e6b02f11c3f0d3e0cba792ce792..56139be07a3ca7a53e6cca422d8ce71304b259a3 100644 (file)
@@ -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;
                                }
index 93e6344a35abd6e96f73ae9343c7f11648ba2101..91fe1a0487d550619158b1e959246679eba03619 100644 (file)
@@ -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->session->clear();
+                               $this->baseUrl->redirect();
+                       }
 
-                               $this->setForUser($a, $user);
+                       $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 (file)
index 0000000..2c31f32
--- /dev/null
@@ -0,0 +1,252 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2024, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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;
+       }
+}
index 291cd0fd593f66135bec32c9525e70f8c8ed8247..7721dbfdddd6f3885f2cb6d171c475bc0b2aae26 100644 (file)
@@ -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());
+       }
 }
index 557f7bbed0324e163fb59c559dc1d1fcc5720390..9dc43f561b11d61d6770da5ea79cc05358c54474 100644 (file)
@@ -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 <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\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 \"<strong>nickname@%s</strong>\"."
 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:<br> login: %s<br> "
 "password: %s<br><br>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 ""