Merge pull request #14185 from annando/authentication

OpenWebAuth moved to a separate class / Improved authentication handling
This commit is contained in:
Hypolite Petovan 2024-05-28 19:22:10 -04:00 committed by GitHub
commit eb2a8e47b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 595 additions and 410 deletions

View file

@ -39,11 +39,10 @@ use Friendica\Core\L10n;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Core\Theme; use Friendica\Core\Theme;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Module\Special\HTTPException as ModuleHTTPException;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\ATProtocol\DID; use Friendica\Protocol\ATProtocol\DID;
use Friendica\Security\OpenWebAuth;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPInputData; use Friendica\Util\HTTPInputData;
use Friendica\Util\HTTPSignature; use Friendica\Util\HTTPSignature;
@ -94,6 +93,9 @@ class App
/** @var string The name of the current mobile theme */ /** @var string The name of the current mobile theme */
private $currentMobileTheme; private $currentMobileTheme;
/** @var Authentication */
private $auth;
/** /**
* @var IManageConfigValues The config * @var IManageConfigValues The config
*/ */
@ -279,8 +281,9 @@ class App
* @param DbaDefinition $dbaDefinition * @param DbaDefinition $dbaDefinition
* @param ViewDefinition $viewDefinition * @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->database = $database;
$this->config = $config; $this->config = $config;
$this->mode = $mode; $this->mode = $mode;
@ -563,7 +566,7 @@ class App
if ($this->mode->isNormal() && !$this->mode->isBackend()) { if ($this->mode->isNormal() && !$this->mode->isBackend()) {
$requester = HTTPSignature::getSigner('', $server); $requester = HTTPSignature::getSigner('', $server);
if (!empty($requester)) { 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 // Valid profile links contain a path with "/profile/" and no query parameters
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') && if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') &&
strpos(parse_url($_GET['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) { strpos(parse_url($_GET['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) {
if ($this->session->get('visitor_home') != $_GET['zrl']) { $this->auth->setUnauthenticatedVisitor($_GET['zrl']);
$this->session->set('my_url', $_GET['zrl']); OpenWebAuth::zrlInit();
$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();
} else { } else {
// Someone came with an invalid parameter, maybe as a DDoS attempt // Someone came with an invalid parameter, maybe as a DDoS attempt
// We simply stop processing here // We simply stop processing here
@ -594,14 +588,14 @@ class App
if (!empty($_GET['owt']) && $this->mode->isNormal()) { if (!empty($_GET['owt']) && $this->mode->isNormal()) {
$token = $_GET['owt']; $token = $_GET['owt'];
Model\Profile::openWebAuthInit($token); OpenWebAuth::init($token);
} }
if (!$this->mode->isBackend()) { if (!$this->mode->isBackend()) {
$auth->withSession($this); $auth->withSession($this);
} }
if (empty($_SESSION['authenticated'])) { if ($this->session->isUnauthenticated()) {
header('X-Account-Management-Status: none'); header('X-Account-Management-Status: none');
} }

View file

@ -30,12 +30,12 @@ use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Conversation\Community; use Friendica\Module\Conversation\Community;
use Friendica\Module\Home; use Friendica\Module\Home;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Security\OpenWebAuth;
class Nav class Nav
{ {
@ -281,7 +281,7 @@ class Nav
$gdirpath = 'directory'; $gdirpath = 'directory';
if ($this->config->get('system', 'singleuser') && $this->config->get('system', '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) && if (Feature::isEnabled($this->session->getLocalUserId(), Feature::COMMUNITY) && (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&

View file

@ -32,7 +32,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Circle; use Friendica\Model\Circle;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Profile; use Friendica\Security\OpenWebAuth;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal; use Friendica\Util\Temporal;
@ -85,7 +85,7 @@ class Widget
$nv['random'] = DI::l10n()->t('Random Profile'); $nv['random'] = DI::l10n()->t('Random Profile');
$nv['inv'] = DI::l10n()->t('Invite Friends'); $nv['inv'] = DI::l10n()->t('Invite Friends');
$nv['directory'] = DI::l10n()->t('Global Directory'); $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'); $nv['local_directory'] = DI::l10n()->t('Local Directory');
$aside = []; $aside = [];

View file

@ -80,9 +80,9 @@ interface IHandleUserSessions extends IHandleSessions
public function getMyUrl(): string; 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; public function isAuthenticated(): bool;
@ -100,6 +100,20 @@ interface IHandleUserSessions extends IHandleSessions
*/ */
public function isModerator(): bool; 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 * Returns User ID of the managed user in case it's a different identity
* *

View file

@ -130,7 +130,7 @@ class UserSession implements IHandleUserSessions
/** {@inheritDoc} */ /** {@inheritDoc} */
public function isAuthenticated(): bool public function isAuthenticated(): bool
{ {
return $this->session->get('authenticated', false); return $this->session->get('authenticated', false) && $this->getLocalUserId();
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -145,6 +145,18 @@ class UserSession implements IHandleUserSessions
return User::isModerator($this->getLocalUserId()); 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} */ /** {@inheritDoc} */
public function setVisitorsContacts(string $my_url) public function setVisitorsContacts(string $my_url)
{ {

View file

@ -31,21 +31,14 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Search; use Friendica\Core\Search;
use Friendica\Core\System;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; 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;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Security\PermissionSet\Entity\PermissionSet; use Friendica\Security\PermissionSet\Entity\PermissionSet;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Network;
use Friendica\Util\Proxy; use Friendica\Util\Proxy;
use Friendica\Util\Strings; 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. * Get the user ID of the page owner.
* *

View file

@ -32,12 +32,12 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Navigation\SystemMessages; use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPException\ForbiddenException; use Friendica\Network\HTTPException\ForbiddenException;
use Friendica\Network\Probe; use Friendica\Network\Probe;
use Friendica\Security\OpenWebAuth;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use GuzzleHttp\Psr7\Uri; use GuzzleHttp\Psr7\Uri;
@ -175,7 +175,7 @@ class Follow extends BaseModule
'$action' => $requestUrl, '$action' => $requestUrl,
'$name' => $contact['name'], '$name' => $contact['name'],
'$url' => $contact['url'], '$url' => $contact['url'],
'$zrl' => Profile::zrl($contact['url']), '$zrl' => OpenWebAuth::getZrlUrl($contact['url']),
'$myaddr' => $myaddr, '$myaddr' => $myaddr,
'$keywords' => $contact['keywords'], '$keywords' => $contact['keywords'],

View file

@ -32,6 +32,7 @@ use Friendica\DI;
use Friendica\Model; use Friendica\Model;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Security\OpenWebAuth;
/** /**
* Shows the local directory of this node * Shows the local directory of this node
@ -63,7 +64,7 @@ class Directory extends BaseModule
$gDirPath = ''; $gDirPath = '';
$dirURL = Search::getGlobalDirectory(); $dirURL = Search::getGlobalDirectory();
if (strlen($dirURL)) { if (strlen($dirURL)) {
$gDirPath = Profile::zrl($dirURL, true); $gDirPath = OpenWebAuth::getZrlUrl($dirURL, true);
} }
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 60); $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 60);

View file

@ -21,7 +21,6 @@
namespace Friendica\Module; namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Contact\Header; use Friendica\Contact\Header;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
@ -30,7 +29,6 @@ use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Photo as MPhoto; use Friendica\Model\Photo as MPhoto;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Core\Storage\Type\ExternalResource; use Friendica\Core\Storage\Type\ExternalResource;
use Friendica\Core\Storage\Type\SystemResource; use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Core\System; use Friendica\Core\System;
@ -42,8 +40,8 @@ use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\NotModifiedException; use Friendica\Network\HTTPException\NotModifiedException;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Security\OpenWebAuth;
use Friendica\Util\Images; use Friendica\Util\Images;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl; use Friendica\Util\ParseUrl;
use Friendica\Util\Proxy; use Friendica\Util\Proxy;
use Friendica\Worker\UpdateContact; use Friendica\Worker\UpdateContact;
@ -78,7 +76,7 @@ class Photo extends BaseApi
throw new NotModifiedException(); throw new NotModifiedException();
} }
Profile::addVisitorCookieForHTTPSigner($this->server); OpenWebAuth::addVisitorCookieForHTTPSigner($this->server);
$customsize = 0; $customsize = 0;
$square_resize = true; $square_resize = true;

View file

@ -29,6 +29,7 @@ use Friendica\Core\Hook;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
@ -51,11 +52,16 @@ class Register extends BaseModule
/** @var Tos */ /** @var Tos */
protected $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); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->tos = new Tos($l10n, $baseUrl, $args, $logger, $profiler, $response, $config, $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: case self::CLOSED:
default: default:
if (empty($_SESSION['authenticated']) && empty($_SESSION['administrator'])) { if (!$this->session->isSiteAdmin()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.')); DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return; return;
} }

View file

@ -34,12 +34,12 @@ use Friendica\DI;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Security\TwoFactor\Repository\TrustedBrowser; use Friendica\Security\TwoFactor\Repository\TrustedBrowser;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\Network;
use LightOpenID; use LightOpenID;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -146,7 +146,7 @@ class Authentication
$this->cookie->send(); $this->cookie->send();
// Do the authentication if not done by now // Do the authentication if not done by now
if (!$this->session->get('authenticated')) { if (!$this->session->isAuthenticated()) {
$this->setForUser($a, $user); $this->setForUser($a, $user);
if ($this->config->get('system', 'paranoia')) { if ($this->config->get('system', 'paranoia')) {
@ -156,46 +156,44 @@ class Authentication
} }
} }
if ($this->session->get('authenticated')) { if ($this->session->isVisitor()) {
if ($this->session->get('visitor_id') && !$this->session->get('uid')) { $contact = $this->dba->selectFirst('contact', ['id'], ['id' => $this->session->get('visitor_id')]);
$contact = $this->dba->selectFirst('contact', ['id'], ['id' => $this->session->get('visitor_id')]); if ($this->dba->isResult($contact)) {
if ($this->dba->isResult($contact)) { $a->setContactId($contact['id']);
$a->setContactId($contact['id']);
}
} }
}
if ($this->session->get('uid')) { if ($this->session->isAuthenticated()) {
// already logged in user returning // already logged in user returning
$check = $this->config->get('system', 'paranoia'); $check = $this->config->get('system', 'paranoia');
// extra paranoia - if the IP changed, log them out // extra paranoia - if the IP changed, log them out
if ($check && ($this->session->get('addr') != $this->remoteAddress)) { if ($check && ($this->session->get('addr') != $this->remoteAddress)) {
$this->logger->notice('Session address changed. Paranoid setting in effect, blocking session. ', [ $this->logger->notice('Session address changed. Paranoid setting in effect, blocking session. ', [
'addr' => $this->session->get('addr'), 'addr' => $this->session->get('addr'),
'remote_addr' => $this->remoteAddress '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->dba->isResult($user)) { $this->session->clear();
$this->session->clear(); $this->baseUrl->redirect();
$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'); $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']);
}
}
} }

View file

@ -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;
}
}

View file

@ -190,6 +190,7 @@ class UserSessionTest extends MockedTest
'authenticated' => [ 'authenticated' => [
'data' => [ 'data' => [
'authenticated' => true, 'authenticated' => true,
'uid' => 21,
], ],
'expected' => true, 'expected' => true,
], ],
@ -199,6 +200,13 @@ class UserSessionTest extends MockedTest
], ],
'expected' => false, 'expected' => false,
], ],
'remote_visitor' => [
'data' => [
'authenticated' => true,
'visitor_id' => 21,
],
'expected' => false,
],
'missing' => [ 'missing' => [
'data' => [ 'data' => [
], ],
@ -215,4 +223,104 @@ class UserSessionTest extends MockedTest
$userSession = new UserSession(new ArraySession($data)); $userSession = new UserSession(new ArraySession($data));
$this->assertEquals($expected, $userSession->isAuthenticated()); $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());
}
} }

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2024.06-dev\n" "Project-Id-Version: 2024.06-dev\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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/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/Contacts.php:78 src/Module/Profile/Photos.php:92
#: src/Module/Profile/Schedule.php:39 src/Module/Profile/Schedule.php:56 #: 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:84 src/Module/Register.php:97
#: src/Module/Register.php:207 src/Module/Register.php:246 #: src/Module/Register.php:213 src/Module/Register.php:252
#: src/Module/Search/Directory.php:37 src/Module/Settings/Account.php:50 #: 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/Account.php:386 src/Module/Settings/Channels.php:66
#: src/Module/Settings/Channels.php:141 src/Module/Settings/Delegation.php:90 #: src/Module/Settings/Channels.php:141 src/Module/Settings/Delegation.php:90
@ -388,14 +388,14 @@ msgid "Save"
msgstr "" msgstr ""
#: mod/photos.php:66 mod/photos.php:129 mod/photos.php:573 #: 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/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/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/Common.php:62 src/Module/Profile/Common.php:71
#: src/Module/Profile/Contacts.php:64 src/Module/Profile/Contacts.php:72 #: 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/Conversations.php:91 src/Module/Profile/Media.php:56
#: src/Module/Profile/Photos.php:83 src/Module/Profile/RemoteFollow.php:71 #: 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." msgid "User not found."
msgstr "" msgstr ""
@ -449,7 +449,7 @@ msgid "%1$s was tagged in %2$s by %3$s"
msgstr "" msgstr ""
#: mod/photos.php:577 src/Module/Conversation/Community.php:160 #: 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 #: src/Module/Search/Index.php:65
msgid "Public access denied." msgid "Public access denied."
msgstr "" msgstr ""
@ -656,11 +656,11 @@ msgstr ""
msgid "Map" msgid "Map"
msgstr "" msgstr ""
#: src/App.php:438 #: src/App.php:441
msgid "No system theme config value set." msgid "No system theme config value set."
msgstr "" msgstr ""
#: src/App.php:546 #: src/App.php:549
msgid "Apologies but the website is unavailable at the moment." msgid "Apologies but the website is unavailable at the moment."
msgstr "" msgstr ""
@ -1384,7 +1384,7 @@ msgid "Public post"
msgstr "" msgstr ""
#: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:131 #: 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 #: src/Module/Post/Edit.php:181
msgid "Message" msgid "Message"
msgstr "" msgstr ""
@ -1891,7 +1891,7 @@ msgstr ""
#: src/Content/Item.php:430 src/Content/Item.php:453 src/Model/Contact.php:1168 #: 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/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" msgid "View Profile"
msgstr "" msgstr ""
@ -1900,7 +1900,7 @@ msgid "View Photos"
msgstr "" msgstr ""
#: src/Content/Item.php:432 src/Model/Contact.php:1202 #: src/Content/Item.php:432 src/Model/Contact.php:1202
#: src/Model/Profile.php:468 #: src/Model/Profile.php:461
msgid "Network Posts" msgid "Network Posts"
msgstr "" msgstr ""
@ -2058,7 +2058,7 @@ msgstr ""
msgid "Home Page" msgid "Home Page"
msgstr "" 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 #: src/Module/Security/Login.php:124
msgid "Register" msgid "Register"
msgstr "" msgstr ""
@ -2138,7 +2138,7 @@ msgid "Information about this friendica instance"
msgstr "" msgstr ""
#: src/Content/Nav.php:301 src/Module/Admin/Tos.php:78 #: 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 #: src/Module/Tos.php:101
msgid "Terms of Service" msgid "Terms of Service"
msgstr "" msgstr ""
@ -2313,7 +2313,7 @@ msgid "The end"
msgstr "" msgstr ""
#: src/Content/Text/HTML.php:861 src/Content/Widget/VCard.php:127 #: 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" msgid "Follow"
msgstr "" msgstr ""
@ -2353,7 +2353,7 @@ msgid "Examples: Robert Morgenstein, Fishing"
msgstr "" msgstr ""
#: src/Content/Widget.php:82 src/Module/Contact.php:460 #: 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" msgid "Find"
msgstr "" msgstr ""
@ -2374,7 +2374,7 @@ msgstr ""
msgid "Invite Friends" msgid "Invite Friends"
msgstr "" 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 #: view/theme/vier/theme.php:202
msgid "Global Directory" msgid "Global Directory"
msgstr "" msgstr ""
@ -2486,46 +2486,46 @@ msgid "More Trending Tags"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:105 src/Model/Contact.php:1196 #: 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" msgid "Post to group"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:110 src/Model/Contact.php:1200 #: 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" msgid "Mention"
msgstr "" 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 #: src/Module/Contact/Profile.php:414 src/Module/Profile/Profile.php:199
msgid "XMPP:" msgid "XMPP:"
msgstr "" 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 #: src/Module/Contact/Profile.php:416 src/Module/Profile/Profile.php:203
msgid "Matrix:" msgid "Matrix:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:122 src/Model/Event.php:82 #: 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/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/Model/Profile.php:369 src/Module/Contact/Profile.php:412
#: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 #: src/Module/Directory.php:148 src/Module/Notifications/Introductions.php:187
#: src/Module/Profile/Profile.php:221 #: src/Module/Profile/Profile.php:221
msgid "Location:" msgid "Location:"
msgstr "" 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 #: src/Module/Notifications/Introductions.php:201
msgid "Network:" msgid "Network:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:129 src/Model/Contact.php:1228 #: 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 #: src/Module/Contact/Profile.php:470
msgid "Unfollow" msgid "Unfollow"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:135 src/Model/Contact.php:1198 #: src/Content/Widget/VCard.php:135 src/Model/Contact.php:1198
#: src/Model/Profile.php:464 #: src/Model/Profile.php:457
msgid "View group" msgid "View group"
msgstr "" msgstr ""
@ -3581,149 +3581,144 @@ msgstr ""
msgid "Wall Photos" msgid "Wall Photos"
msgstr "" 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 #: src/Module/Profile/Profile.php:285
msgid "Edit profile" msgid "Edit profile"
msgstr "" msgstr ""
#: src/Model/Profile.php:366 #: src/Model/Profile.php:359
msgid "Change profile photo" msgid "Change profile photo"
msgstr "" 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 #: src/Module/Profile/Profile.php:209
msgid "Homepage:" msgid "Homepage:"
msgstr "" 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 #: src/Module/Notifications/Introductions.php:189
msgid "About:" msgid "About:"
msgstr "" msgstr ""
#: src/Model/Profile.php:481 #: src/Model/Profile.php:474
msgid "Atom feed" msgid "Atom feed"
msgstr "" msgstr ""
#: src/Model/Profile.php:488 #: src/Model/Profile.php:481
msgid "This website has been verified to belong to the same person." msgid "This website has been verified to belong to the same person."
msgstr "" msgstr ""
#: src/Model/Profile.php:539 #: src/Model/Profile.php:532
msgid "F d" msgid "F d"
msgstr "" msgstr ""
#: src/Model/Profile.php:603 src/Model/Profile.php:680 #: src/Model/Profile.php:596 src/Model/Profile.php:673
msgid "[today]" msgid "[today]"
msgstr "" msgstr ""
#: src/Model/Profile.php:612 #: src/Model/Profile.php:605
msgid "Birthday Reminders" msgid "Birthday Reminders"
msgstr "" msgstr ""
#: src/Model/Profile.php:613 #: src/Model/Profile.php:606
msgid "Birthdays this week:" msgid "Birthdays this week:"
msgstr "" msgstr ""
#: src/Model/Profile.php:629 #: src/Model/Profile.php:622
msgid "g A l F d" msgid "g A l F d"
msgstr "" msgstr ""
#: src/Model/Profile.php:667 #: src/Model/Profile.php:660
msgid "[No description]" msgid "[No description]"
msgstr "" msgstr ""
#: src/Model/Profile.php:693 #: src/Model/Profile.php:686
msgid "Event Reminders" msgid "Event Reminders"
msgstr "" msgstr ""
#: src/Model/Profile.php:694 #: src/Model/Profile.php:687
msgid "Upcoming events the next 7 days:" msgid "Upcoming events the next 7 days:"
msgstr "" msgstr ""
#: src/Model/Profile.php:876 #: src/Model/Profile.php:797
#, php-format
msgid "OpenWebAuth: %1$s welcomes %2$s"
msgstr ""
#: src/Model/Profile.php:1016
msgid "Hometown:" msgid "Hometown:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1017 #: src/Model/Profile.php:798
msgid "Marital Status:" msgid "Marital Status:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1018 #: src/Model/Profile.php:799
msgid "With:" msgid "With:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1019 #: src/Model/Profile.php:800
msgid "Since:" msgid "Since:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1020 #: src/Model/Profile.php:801
msgid "Sexual Preference:" msgid "Sexual Preference:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1021 #: src/Model/Profile.php:802
msgid "Political Views:" msgid "Political Views:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1022 #: src/Model/Profile.php:803
msgid "Religious Views:" msgid "Religious Views:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1023 #: src/Model/Profile.php:804
msgid "Likes:" msgid "Likes:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1024 #: src/Model/Profile.php:805
msgid "Dislikes:" msgid "Dislikes:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1025 #: src/Model/Profile.php:806
msgid "Title/Description:" msgid "Title/Description:"
msgstr "" 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/Report/Create.php:280
#: src/Module/Moderation/Summary.php:76 #: src/Module/Moderation/Summary.php:76
msgid "Summary" msgid "Summary"
msgstr "" msgstr ""
#: src/Model/Profile.php:1027 #: src/Model/Profile.php:808
msgid "Musical interests" msgid "Musical interests"
msgstr "" msgstr ""
#: src/Model/Profile.php:1028 #: src/Model/Profile.php:809
msgid "Books, literature" msgid "Books, literature"
msgstr "" msgstr ""
#: src/Model/Profile.php:1029 #: src/Model/Profile.php:810
msgid "Television" msgid "Television"
msgstr "" msgstr ""
#: src/Model/Profile.php:1030 #: src/Model/Profile.php:811
msgid "Film/dance/culture/entertainment" msgid "Film/dance/culture/entertainment"
msgstr "" msgstr ""
#: src/Model/Profile.php:1031 #: src/Model/Profile.php:812
msgid "Hobbies/Interests" msgid "Hobbies/Interests"
msgstr "" msgstr ""
#: src/Model/Profile.php:1032 #: src/Model/Profile.php:813
msgid "Love/romance" msgid "Love/romance"
msgstr "" msgstr ""
#: src/Model/Profile.php:1033 #: src/Model/Profile.php:814
msgid "Work/employment" msgid "Work/employment"
msgstr "" msgstr ""
#: src/Model/Profile.php:1034 #: src/Model/Profile.php:815
msgid "School/education" msgid "School/education"
msgstr "" msgstr ""
#: src/Model/Profile.php:1035 #: src/Model/Profile.php:816
msgid "Contact information and Social Networks" msgid "Contact information and Social Networks"
msgstr "" msgstr ""
@ -3777,13 +3772,13 @@ msgstr ""
msgid "Invalid OpenID url" msgid "Invalid OpenID url"
msgstr "" msgstr ""
#: src/Model/User.php:1218 src/Security/Authentication.php:230 #: src/Model/User.php:1218 src/Security/Authentication.php:228
msgid "" msgid ""
"We encountered a problem while logging in with the OpenID you provided. " "We encountered a problem while logging in with the OpenID you provided. "
"Please check the correct spelling of the ID." "Please check the correct spelling of the ID."
msgstr "" 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:" msgid "The error message was:"
msgstr "" msgstr ""
@ -4135,14 +4130,14 @@ msgstr ""
#: src/Module/Admin/Features.php:67 #: src/Module/Admin/Features.php:67
#: src/Module/Notifications/Introductions.php:144 #: 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 #: src/Module/Settings/TwoFactor/Trusted.php:129
msgid "No" msgid "No"
msgstr "" msgstr ""
#: src/Module/Admin/Features.php:67 src/Module/Contact/Revoke.php:108 #: src/Module/Admin/Features.php:67 src/Module/Contact/Revoke.php:108
#: src/Module/Notifications/Introductions.php:144 #: 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 #: src/Module/Settings/TwoFactor/Trusted.php:129
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
@ -4516,7 +4511,7 @@ msgstr ""
msgid "Republish users to directory" msgid "Republish users to directory"
msgstr "" 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" msgid "Registration"
msgstr "" msgstr ""
@ -6242,7 +6237,7 @@ msgstr ""
#: src/Module/Moderation/Blocklist/Server/Index.php:87 #: src/Module/Moderation/Blocklist/Server/Index.php:87
#: src/Module/Moderation/Blocklist/Server/Index.php:115 #: src/Module/Moderation/Blocklist/Server/Index.php:115
#: src/Module/Moderation/Blocklist/Server/Index.php:116 #: 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/Security/TwoFactor/Verify.php:101
#: src/Module/Settings/Channels.php:190 src/Module/Settings/Channels.php:211 #: src/Module/Settings/Channels.php:190 src/Module/Settings/Channels.php:211
#: src/Module/Settings/TwoFactor/Index.php:161 #: src/Module/Settings/TwoFactor/Index.php:161
@ -7432,19 +7427,19 @@ msgstr ""
msgid "Lookup address:" msgid "Lookup address:"
msgstr "" msgstr ""
#: src/Module/Directory.php:74 #: src/Module/Directory.php:75
msgid "No entries (some entries may be hidden)." msgid "No entries (some entries may be hidden)."
msgstr "" msgstr ""
#: src/Module/Directory.php:90 #: src/Module/Directory.php:91
msgid "Find on this site" msgid "Find on this site"
msgstr "" msgstr ""
#: src/Module/Directory.php:92 #: src/Module/Directory.php:93
msgid "Results for:" msgid "Results for:"
msgstr "" msgstr ""
#: src/Module/Directory.php:94 #: src/Module/Directory.php:95
msgid "Site Directory" msgid "Site Directory"
msgstr "" msgstr ""
@ -9013,21 +9008,21 @@ msgstr ""
msgid "ignored" msgid "ignored"
msgstr "" msgstr ""
#: src/Module/Photo.php:124 #: src/Module/Photo.php:122
msgid "The Photo is not available." msgid "The Photo is not available."
msgstr "" msgstr ""
#: src/Module/Photo.php:149 #: src/Module/Photo.php:147
#, php-format #, php-format
msgid "The Photo with id %s is not available." msgid "The Photo with id %s is not available."
msgstr "" msgstr ""
#: src/Module/Photo.php:190 #: src/Module/Photo.php:188
#, php-format #, php-format
msgid "Invalid external resource with url %s." msgid "Invalid external resource with url %s."
msgstr "" msgstr ""
#: src/Module/Photo.php:192 #: src/Module/Photo.php:190
#, php-format #, php-format
msgid "Invalid photo with id %s." msgid "Invalid photo with id %s."
msgstr "" msgstr ""
@ -9301,170 +9296,170 @@ msgstr ""
msgid "Remove post" msgid "Remove post"
msgstr "" msgstr ""
#: src/Module/Register.php:85 #: src/Module/Register.php:91
msgid "Only parent users can create additional accounts." msgid "Only parent users can create additional accounts."
msgstr "" msgstr ""
#: src/Module/Register.php:100 src/Module/User/Import.php:112 #: src/Module/Register.php:106 src/Module/User/Import.php:112
msgid "" msgid ""
"This site has exceeded the number of allowed daily account registrations. " "This site has exceeded the number of allowed daily account registrations. "
"Please try again tomorrow." "Please try again tomorrow."
msgstr "" msgstr ""
#: src/Module/Register.php:117 #: src/Module/Register.php:123
msgid "" msgid ""
"You may (optionally) fill in this form via OpenID by supplying your OpenID " "You may (optionally) fill in this form via OpenID by supplying your OpenID "
"and clicking \"Register\"." "and clicking \"Register\"."
msgstr "" msgstr ""
#: src/Module/Register.php:118 #: src/Module/Register.php:124
msgid "" msgid ""
"If you are not familiar with OpenID, please leave that field blank and fill " "If you are not familiar with OpenID, please leave that field blank and fill "
"in the rest of the items." "in the rest of the items."
msgstr "" msgstr ""
#: src/Module/Register.php:119 #: src/Module/Register.php:125
msgid "Your OpenID (optional): " msgid "Your OpenID (optional): "
msgstr "" msgstr ""
#: src/Module/Register.php:128 #: src/Module/Register.php:134
msgid "Include your profile in member directory?" msgid "Include your profile in member directory?"
msgstr "" msgstr ""
#: src/Module/Register.php:149 #: src/Module/Register.php:155
msgid "Note for the admin" msgid "Note for the admin"
msgstr "" 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" msgid "Leave a message for the admin, why you want to join this node"
msgstr "" msgstr ""
#: src/Module/Register.php:150 #: src/Module/Register.php:156
msgid "Membership on this site is by invitation only." msgid "Membership on this site is by invitation only."
msgstr "" msgstr ""
#: src/Module/Register.php:151 #: src/Module/Register.php:157
msgid "Your invitation code: " msgid "Your invitation code: "
msgstr "" 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" msgid "Your Display Name (as you would like it to be displayed on this system"
msgstr "" msgstr ""
#: src/Module/Register.php:160 #: src/Module/Register.php:166
msgid "" msgid ""
"Your Email Address: (Initial information will be send there, so this has to " "Your Email Address: (Initial information will be send there, so this has to "
"be an existing address.)" "be an existing address.)"
msgstr "" msgstr ""
#: src/Module/Register.php:161 #: src/Module/Register.php:167
msgid "Please repeat your e-mail address:" msgid "Please repeat your e-mail address:"
msgstr "" 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 #: src/Module/Settings/Account.php:564
msgid "New Password:" msgid "New Password:"
msgstr "" msgstr ""
#: src/Module/Register.php:163 #: src/Module/Register.php:169
msgid "Leave empty for an auto generated password." msgid "Leave empty for an auto generated password."
msgstr "" 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 #: src/Module/Settings/Account.php:565
msgid "Confirm:" msgid "Confirm:"
msgstr "" msgstr ""
#: src/Module/Register.php:165 #: src/Module/Register.php:171
#, php-format #, php-format
msgid "" msgid ""
"Choose a profile nickname. This must begin with a text character. Your " "Choose a profile nickname. This must begin with a text character. Your "
"profile address on this site will then be \"<strong>nickname@%s</strong>\"." "profile address on this site will then be \"<strong>nickname@%s</strong>\"."
msgstr "" msgstr ""
#: src/Module/Register.php:166 #: src/Module/Register.php:172
msgid "Choose a nickname: " msgid "Choose a nickname: "
msgstr "" 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" msgid "Import"
msgstr "" msgstr ""
#: src/Module/Register.php:175 #: src/Module/Register.php:181
msgid "Import your profile to this friendica instance" msgid "Import your profile to this friendica instance"
msgstr "" msgstr ""
#: src/Module/Register.php:182 #: src/Module/Register.php:188
msgid "Note: This node explicitly contains adult content" msgid "Note: This node explicitly contains adult content"
msgstr "" 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:" msgid "Parent Password:"
msgstr "" msgstr ""
#: src/Module/Register.php:184 src/Module/Settings/Delegation.php:181 #: src/Module/Register.php:190 src/Module/Settings/Delegation.php:181
msgid "" msgid ""
"Please enter the password of the parent account to legitimize your request." "Please enter the password of the parent account to legitimize your request."
msgstr "" msgstr ""
#: src/Module/Register.php:213 #: src/Module/Register.php:219
msgid "Password doesn't match." msgid "Password doesn't match."
msgstr "" msgstr ""
#: src/Module/Register.php:219 #: src/Module/Register.php:225
msgid "Please enter your password." msgid "Please enter your password."
msgstr "" msgstr ""
#: src/Module/Register.php:261 #: src/Module/Register.php:267
msgid "You have entered too much information." msgid "You have entered too much information."
msgstr "" msgstr ""
#: src/Module/Register.php:284 #: src/Module/Register.php:290
msgid "Please enter the identical mail address in the second field." msgid "Please enter the identical mail address in the second field."
msgstr "" msgstr ""
#: src/Module/Register.php:292 #: src/Module/Register.php:298
msgid "Nickname cannot start with a digit." msgid "Nickname cannot start with a digit."
msgstr "" msgstr ""
#: src/Module/Register.php:294 #: src/Module/Register.php:300
msgid "Nickname can only contain US-ASCII characters." msgid "Nickname can only contain US-ASCII characters."
msgstr "" msgstr ""
#: src/Module/Register.php:323 #: src/Module/Register.php:329
msgid "The additional account was created." msgid "The additional account was created."
msgstr "" msgstr ""
#: src/Module/Register.php:348 #: src/Module/Register.php:354
msgid "" msgid ""
"Registration successful. Please check your email for further instructions." "Registration successful. Please check your email for further instructions."
msgstr "" msgstr ""
#: src/Module/Register.php:355 #: src/Module/Register.php:361
#, php-format #, php-format
msgid "" msgid ""
"Failed to send email message. Here your accout details:<br> login: %s<br> " "Failed to send email message. Here your accout details:<br> login: %s<br> "
"password: %s<br><br>You can change your password after login." "password: %s<br><br>You can change your password after login."
msgstr "" msgstr ""
#: src/Module/Register.php:361 #: src/Module/Register.php:367
msgid "Registration successful." msgid "Registration successful."
msgstr "" msgstr ""
#: src/Module/Register.php:370 src/Module/Register.php:377 #: src/Module/Register.php:376 src/Module/Register.php:383
#: src/Module/Register.php:387 #: src/Module/Register.php:393
msgid "Your registration can not be processed." msgid "Your registration can not be processed."
msgstr "" msgstr ""
#: src/Module/Register.php:376 #: src/Module/Register.php:382
msgid "You have to leave a request note for the admin." msgid "You have to leave a request note for the admin."
msgstr "" msgstr ""
#: src/Module/Register.php:386 #: src/Module/Register.php:392
msgid "An internal error occured." msgid "An internal error occured."
msgstr "" msgstr ""
#: src/Module/Register.php:408 #: src/Module/Register.php:414
msgid "Your registration is pending approval by the site owner." msgid "Your registration is pending approval by the site owner."
msgstr "" msgstr ""
@ -12588,23 +12583,28 @@ msgstr ""
msgid "The folder %s must be writable by webserver." msgid "The folder %s must be writable by webserver."
msgstr "" msgstr ""
#: src/Security/Authentication.php:216 #: src/Security/Authentication.php:214
msgid "Login failed." msgid "Login failed."
msgstr "" msgstr ""
#: src/Security/Authentication.php:261 #: src/Security/Authentication.php:259
msgid "Login failed. Please check your credentials." msgid "Login failed. Please check your credentials."
msgstr "" msgstr ""
#: src/Security/Authentication.php:375 #: src/Security/Authentication.php:373
#, php-format #, php-format
msgid "Welcome %s" msgid "Welcome %s"
msgstr "" msgstr ""
#: src/Security/Authentication.php:376 #: src/Security/Authentication.php:374
msgid "Please upload a profile photo." msgid "Please upload a profile photo."
msgstr "" msgstr ""
#: src/Security/OpenWebAuth.php:163
#, php-format
msgid "OpenWebAuth: %1$s welcomes %2$s"
msgstr ""
#: src/Util/EMailer/MailBuilder.php:260 #: src/Util/EMailer/MailBuilder.php:260
msgid "Friendica Notification" msgid "Friendica Notification"
msgstr "" msgstr ""