2018-06-18 23:05:44 +02:00
< ? php
2024-08-24 15:27:00 +02:00
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
2020-02-09 16:18:46 +01:00
2018-06-18 23:05:44 +02:00
namespace Friendica\Module ;
2023-05-15 20:46:05 +00:00
use Exception ;
2024-11-08 23:04:52 +00:00
use Friendica\App\Arguments ;
use Friendica\App\BaseURL ;
use Friendica\AppHelper ;
2018-06-18 23:05:44 +02:00
use Friendica\BaseModule ;
2021-11-19 20:18:48 +01:00
use Friendica\Core\L10n ;
2024-05-20 19:36:40 +00:00
use Friendica\Core\Protocol ;
2022-10-20 23:35:01 +02:00
use Friendica\Core\Session\Capability\IHandleUserSessions ;
2018-10-29 17:20:46 -04:00
use Friendica\Core\System ;
2024-05-20 19:36:40 +00:00
use Friendica\Core\Worker ;
2021-11-19 20:18:48 +01:00
use Friendica\Database\Database ;
2018-06-19 16:15:28 +02:00
use Friendica\Model\Contact ;
2023-06-18 17:18:40 +00:00
use Friendica\Model\GServer ;
2021-08-08 19:30:21 +00:00
use Friendica\Model\User ;
2021-11-19 20:18:48 +01:00
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests ;
2021-10-23 12:50:31 +02:00
use Friendica\Network\HTTPClient\Client\HttpClientOptions ;
2018-06-20 18:38:23 +02:00
use Friendica\Util\HTTPSignature ;
2024-05-20 19:36:40 +00:00
use Friendica\Util\Network ;
2021-11-20 15:38:03 +01:00
use Friendica\Util\Profiler ;
2018-11-08 08:45:46 -05:00
use Friendica\Util\Strings ;
2024-05-20 19:36:40 +00:00
use Friendica\Worker\UpdateContact ;
2021-11-19 20:18:48 +01:00
use Psr\Log\LoggerInterface ;
2018-06-18 23:05:44 +02:00
2018-06-19 13:30:55 +02:00
/**
* Magic Auth ( remote authentication ) module .
2018-07-10 14:27:56 +02:00
*
2018-06-19 13:30:55 +02:00
* Ported from Hubzilla : https :// framagit . org / hubzilla / core / blob / master / Zotlabs / Module / Magic . php
*/
2018-06-18 23:05:44 +02:00
class Magic extends BaseModule
{
2024-11-08 23:04:52 +00:00
/** @var AppHelper */
protected $appHelper ;
2021-11-19 20:18:48 +01:00
/** @var Database */
protected $dba ;
/** @var ICanSendHttpRequests */
protected $httpClient ;
2022-10-20 23:35:01 +02:00
/** @var IHandleUserSessions */
protected $userSession ;
2021-11-19 20:18:48 +01:00
2024-11-08 23:04:52 +00:00
public function __construct ( AppHelper $appHelper , L10n $l10n , BaseURL $baseUrl , Arguments $args , LoggerInterface $logger , Profiler $profiler , Response $response , Database $dba , ICanSendHttpRequests $httpClient , IHandleUserSessions $userSession , $server , array $parameters = [])
2018-06-18 23:05:44 +02:00
{
2021-11-21 20:06:36 +01:00
parent :: __construct ( $l10n , $baseUrl , $args , $logger , $profiler , $response , $server , $parameters );
2018-06-18 23:05:44 +02:00
2024-11-08 23:04:52 +00:00
$this -> appHelper = $appHelper ;
2022-10-20 23:35:01 +02:00
$this -> dba = $dba ;
$this -> httpClient = $httpClient ;
$this -> userSession = $userSession ;
2021-11-19 20:18:48 +01:00
}
2021-11-20 15:38:03 +01:00
protected function rawContent ( array $request = [])
2021-11-19 20:18:48 +01:00
{
2023-05-15 20:46:05 +00:00
if ( $_SERVER [ 'REQUEST_METHOD' ] == 'HEAD' ) {
$this -> logger -> debug ( 'Got a HEAD request' );
System :: exit ();
}
2021-11-19 20:18:48 +01:00
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'Invoked' , [ 'request' => $request ]);
2018-06-18 23:05:44 +02:00
2024-11-18 08:28:32 +00:00
$addr = ( string ) $request [ 'addr' ] ? ? '' ;
$bdest = ( string ) $request [ 'bdest' ] ? ? '' ;
$dest = ( string ) $request [ 'dest' ] ? ? '' ;
2024-05-20 19:36:40 +00:00
$owa = intval ( $request [ 'owa' ] ? ? 0 );
2018-06-18 23:05:44 +02:00
2023-05-15 20:46:05 +00:00
// bdest is preferred as it is hex-encoded and can survive url rewrite and argument parsing
2024-11-18 08:28:32 +00:00
if ( $bdest !== '' ) {
2023-05-05 12:46:30 +02:00
$dest = hex2bin ( $bdest );
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'bdest detected' , [ 'dest' => $dest ]);
2023-05-05 12:46:30 +02:00
}
2023-05-15 20:46:05 +00:00
2023-06-18 17:18:40 +00:00
$target = $dest ? : $addr ;
2024-11-18 08:28:32 +00:00
$contact = Contact :: getByURL ( $addr ? : $dest );
if ( $contact === [] && $owa === 0 ) {
$this -> logger -> info ( 'No contact record found, no oWA, redirecting to destination.' , [ 'request' => $request , 'server' => $_SERVER , 'dest' => $dest ]);
2024-11-18 21:45:42 +00:00
$this -> appHelper -> redirect ( $dest );
2018-06-18 23:05:44 +02:00
}
2018-06-19 16:15:28 +02:00
2024-11-18 08:28:32 +00:00
if ( $contact !== []) {
2023-05-05 12:46:30 +02:00
// Redirect if the contact is already authenticated on this site.
2024-11-08 23:04:52 +00:00
if ( $this -> appHelper -> getContactId () && strpos ( $contact [ 'nurl' ], Strings :: normaliseLink ( $this -> baseUrl )) !== false ) {
2023-05-15 20:46:05 +00:00
$this -> logger -> info ( 'Contact is already authenticated, redirecting to destination.' , [ 'dest' => $dest ]);
2023-05-05 12:46:30 +02:00
System :: externalRedirect ( $dest );
}
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'Contact found' , [ 'url' => $contact [ 'url' ]]);
}
2024-11-18 08:28:32 +00:00
if ( ! $this -> userSession -> getLocalUserId () || $owa === 0 ) {
2023-05-15 20:46:05 +00:00
$this -> logger -> notice ( 'Not logged in or not OWA, redirecting to destination.' , [ 'uid' => $this -> userSession -> getLocalUserId (), 'owa' => $owa , 'dest' => $dest ]);
2024-11-08 23:04:52 +00:00
$this -> appHelper -> redirect ( $dest );
2018-06-18 23:05:44 +02:00
}
2024-05-20 19:36:40 +00:00
$dest = Network :: removeUrlParameter ( $dest , 'zid' );
$dest = Network :: removeUrlParameter ( $dest , 'f' );
2024-11-18 08:28:32 +00:00
2021-08-08 19:30:21 +00:00
// OpenWebAuth
2023-05-15 20:46:05 +00:00
$owner = User :: getOwnerDataById ( $this -> userSession -> getLocalUserId ());
2021-08-08 19:30:21 +00:00
2023-06-18 17:18:40 +00:00
if ( ! empty ( $contact [ 'gsid' ])) {
2024-05-20 19:36:40 +00:00
$gsid = $contact [ 'gsid' ];
2023-06-18 17:18:40 +00:00
} elseif ( GServer :: check ( $target )) {
2024-05-20 19:36:40 +00:00
$gsid = GServer :: getID ( $target );
}
if ( empty ( $gsid )) {
2023-06-18 17:18:40 +00:00
$this -> logger -> notice ( 'The target is not a server path, redirecting to destination.' , [ 'target' => $target ]);
2023-05-15 20:46:05 +00:00
System :: externalRedirect ( $dest );
}
2024-05-20 19:36:40 +00:00
$gserver = $this -> dba -> selectFirst ( 'gserver' , [ 'url' , 'network' , 'openwebauth' ], [ 'id' => $gsid ]);
if ( empty ( $gserver )) {
$this -> logger -> notice ( 'Server not found, redirecting to destination.' , [ 'gsid' => $gsid , 'dest' => $dest ]);
System :: externalRedirect ( $dest );
}
$openwebauth = $gserver [ 'openwebauth' ];
// This part can be removed, when all server entries had been updated. So removing it in 2025 should be safe.
if ( empty ( $openwebauth ) && ( $gserver [ 'network' ] == Protocol :: DFRN )) {
$this -> logger -> notice ( 'Open Web Auth path not provided. Assume default path' , [ 'gsid' => $gsid , 'dest' => $dest ]);
$openwebauth = $gserver [ 'url' ] . '/owa' ;
// Update contact to assign the path to the server
UpdateContact :: add ( Worker :: PRIORITY_MEDIUM , $contact [ 'id' ]);
}
if ( empty ( $openwebauth )) {
$this -> logger -> debug ( 'Server does not support open web auth, redirecting to destination.' , [ 'gsid' => $gsid , 'dest' => $dest ]);
System :: externalRedirect ( $dest );
}
2023-05-15 20:46:05 +00:00
$header = [
2024-05-20 19:36:40 +00:00
'Accept' => 'application/x-zot+json' ,
2024-05-20 19:36:40 +00:00
'X-Open-Web-Auth' => Strings :: getRandomHex (),
2023-05-15 20:46:05 +00:00
];
// Create a header that is signed with the local users private key.
$header = HTTPSignature :: createSig (
$header ,
$owner [ 'prvkey' ],
'acct:' . $owner [ 'addr' ]
);
2024-05-20 19:36:40 +00:00
$this -> logger -> info ( 'Fetch from remote system' , [ 'openwebauth' => $openwebauth , 'headers' => $header ]);
2023-05-15 20:46:05 +00:00
// Try to get an authentication token from the other instance.
try {
2024-05-20 19:36:40 +00:00
$curlResult = $this -> httpClient -> request ( 'get' , $openwebauth , [ HttpClientOptions :: HEADERS => $header ]);
2023-05-15 20:46:05 +00:00
} catch ( Exception $exception ) {
2024-05-20 19:36:40 +00:00
$this -> logger -> notice ( 'URL is invalid, redirecting to destination.' , [ 'url' => $openwebauth , 'error' => $exception , 'dest' => $dest ]);
2023-05-15 20:46:05 +00:00
System :: externalRedirect ( $dest );
}
if ( ! $curlResult -> isSuccess ()) {
$this -> logger -> notice ( 'OWA request failed, redirecting to destination.' , [ 'returncode' => $curlResult -> getReturnCode (), 'dest' => $dest ]);
2021-08-08 19:30:21 +00:00
System :: externalRedirect ( $dest );
2018-06-18 23:05:44 +02:00
}
2024-01-12 01:12:48 -05:00
$j = json_decode ( $curlResult -> getBodyString (), true );
2023-05-15 20:46:05 +00:00
if ( empty ( $j ) || ! $j [ 'success' ]) {
$this -> logger -> notice ( 'Invalid JSON, redirecting to destination.' , [ 'json' => $j , 'dest' => $dest ]);
2024-11-08 23:04:52 +00:00
$this -> appHelper -> redirect ( $dest );
2023-05-15 20:46:05 +00:00
}
if ( $j [ 'encrypted_token' ]) {
// The token is encrypted. If the local user is really the one the other instance
// thinks they is, the token can be decrypted with the local users public key.
$token = '' ;
openssl_private_decrypt ( Strings :: base64UrlDecode ( $j [ 'encrypted_token' ]), $token , $owner [ 'prvkey' ]);
} else {
$token = $j [ 'token' ];
}
$args = ( strpbrk ( $dest , '?&' ) ? '&' : '?' ) . 'owt=' . $token ;
$this -> logger -> debug ( 'Redirecting' , [ 'path' => $dest . $args ]);
System :: externalRedirect ( $dest . $args );
2018-06-18 23:05:44 +02:00
}
}