2018-06-18 21:05:44 +00:00
< ? php
/**
2023-01-01 14:36:24 +00:00
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
2020-02-09 15:18:46 +00:00
*
* @ 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 />.
*
2018-06-18 21:05:44 +00:00
*/
2020-02-09 15:18:46 +00:00
2018-06-18 21:05:44 +00:00
namespace Friendica\Module ;
2023-05-15 20:46:05 +00:00
use Exception ;
2021-11-19 19:18:48 +00:00
use Friendica\App ;
2018-06-18 21:05:44 +00:00
use Friendica\BaseModule ;
2021-11-19 19:18:48 +00:00
use Friendica\Core\L10n ;
2022-10-20 21:35:01 +00:00
use Friendica\Core\Session\Capability\IHandleUserSessions ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\System ;
2021-11-19 19:18:48 +00:00
use Friendica\Database\Database ;
2018-06-19 14:15:28 +00: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 19:18:48 +00:00
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests ;
2021-10-23 10:50:31 +00:00
use Friendica\Network\HTTPClient\Client\HttpClientOptions ;
2018-06-20 16:38:23 +00:00
use Friendica\Util\HTTPSignature ;
2021-11-20 14:38:03 +00:00
use Friendica\Util\Profiler ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2023-06-18 17:18:40 +00:00
use GuzzleHttp\Psr7\Uri ;
2021-11-19 19:18:48 +00:00
use Psr\Log\LoggerInterface ;
2018-06-18 21:05:44 +00:00
2018-06-19 11:30:55 +00:00
/**
* Magic Auth ( remote authentication ) module .
2018-07-10 12:27:56 +00:00
*
2018-06-19 11:30:55 +00:00
* Ported from Hubzilla : https :// framagit . org / hubzilla / core / blob / master / Zotlabs / Module / Magic . php
*/
2018-06-18 21:05:44 +00:00
class Magic extends BaseModule
{
2021-11-19 19:18:48 +00:00
/** @var App */
protected $app ;
/** @var Database */
protected $dba ;
/** @var ICanSendHttpRequests */
protected $httpClient ;
2022-10-20 21:35:01 +00:00
/** @var IHandleUserSessions */
protected $userSession ;
2021-11-19 19:18:48 +00:00
2022-10-20 21:35:01 +00:00
public function __construct ( App $app , L10n $l10n , App\BaseURL $baseUrl , App\Arguments $args , LoggerInterface $logger , Profiler $profiler , Response $response , Database $dba , ICanSendHttpRequests $httpClient , IHandleUserSessions $userSession , $server , array $parameters = [])
2018-06-18 21:05:44 +00:00
{
2021-11-21 19:06:36 +00:00
parent :: __construct ( $l10n , $baseUrl , $args , $logger , $profiler , $response , $server , $parameters );
2018-06-18 21:05:44 +00:00
2022-10-20 21:35:01 +00:00
$this -> app = $app ;
$this -> dba = $dba ;
$this -> httpClient = $httpClient ;
$this -> userSession = $userSession ;
2021-11-19 19:18:48 +00:00
}
2021-11-20 14:38:03 +00:00
protected function rawContent ( array $request = [])
2021-11-19 19:18:48 +00: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 19:18:48 +00:00
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'Invoked' , [ 'request' => $request ]);
2018-06-18 21:05:44 +00:00
2023-05-05 13:58:25 +00:00
$addr = $request [ 'addr' ] ? ? '' ;
$dest = $request [ 'dest' ] ? ? '' ;
$bdest = $request [ 'bdest' ] ? ? '' ;
$owa = intval ( $request [ 'owa' ] ? ? 0 );
2018-06-18 21:05:44 +00: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
2023-05-05 10:46:30 +00:00
if ( ! empty ( $bdest )) {
$dest = hex2bin ( $bdest );
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'bdest detected' , [ 'dest' => $dest ]);
2023-05-05 10:46:30 +00:00
}
2023-05-15 20:46:05 +00:00
2023-06-18 17:18:40 +00:00
$target = $dest ? : $addr ;
2023-05-16 11:54:46 +00:00
if ( $addr ? : $dest ) {
2023-05-15 20:46:05 +00:00
$contact = Contact :: getByURL ( $addr ? : $dest );
2018-06-18 21:05:44 +00:00
}
2018-06-19 14:15:28 +00:00
2023-05-15 20:46:05 +00:00
if ( empty ( $contact )) {
2023-05-05 10:46:30 +00:00
if ( ! $owa ) {
2023-05-15 20:46:05 +00:00
$this -> logger -> info ( 'No contact record found, no oWA, redirecting to destination.' , [ 'request' => $request , 'server' => $_SERVER , 'dest' => $dest ]);
2023-05-05 10:46:30 +00:00
$this -> app -> redirect ( $dest );
}
} else {
// Redirect if the contact is already authenticated on this site.
if ( $this -> app -> 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 10:46:30 +00:00
System :: externalRedirect ( $dest );
}
2023-05-15 20:46:05 +00:00
$this -> logger -> debug ( 'Contact found' , [ 'url' => $contact [ 'url' ]]);
}
if ( ! $this -> userSession -> getLocalUserId () || ! $owa ) {
$this -> logger -> notice ( 'Not logged in or not OWA, redirecting to destination.' , [ 'uid' => $this -> userSession -> getLocalUserId (), 'owa' => $owa , 'dest' => $dest ]);
$this -> app -> redirect ( $dest );
2018-06-18 21:05:44 +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' ])) {
$gserver = $this -> dba -> selectFirst ( 'gserver' , [ 'url' ], [ 'id' => $contact [ 'gsid' ]]);
if ( empty ( $gserver )) {
$this -> logger -> notice ( 'Server not found, redirecting to destination.' , [ 'gsid' => $contact [ 'gsid' ], 'dest' => $dest ]);
System :: externalRedirect ( $dest );
}
$basepath = $gserver [ 'url' ];
} elseif ( GServer :: check ( $target )) {
$basepath = ( string ) GServer :: cleanUri ( new Uri ( $target ));
} else {
$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 );
}
$header = [
2023-06-17 20:31:25 +00:00
'Accept' => 'application/x-dfrn+json, application/x-zot+json' ,
'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' ]
);
$this -> logger -> info ( 'Fetch from remote system' , [ 'basepath' => $basepath , 'headers' => $header ]);
// Try to get an authentication token from the other instance.
try {
$curlResult = $this -> httpClient -> request ( 'get' , $basepath . '/owa' , [ HttpClientOptions :: HEADERS => $header ]);
} catch ( Exception $exception ) {
$this -> logger -> notice ( 'URL is invalid, redirecting to destination.' , [ 'url' => $basepath , 'error' => $exception , 'dest' => $dest ]);
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 21:05:44 +00:00
}
2023-05-15 20:46:05 +00:00
$j = json_decode ( $curlResult -> getBody (), true );
if ( empty ( $j ) || ! $j [ 'success' ]) {
$this -> logger -> notice ( 'Invalid JSON, redirecting to destination.' , [ 'json' => $j , 'dest' => $dest ]);
$this -> app -> redirect ( $dest );
}
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 21:05:44 +00:00
}
}