2018-02-09 03:49:49 +00:00
< ? php
2020-02-09 14:45:36 +00:00
/**
2023-01-01 14:36:24 +00:00
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
2020-02-09 14:45:36 +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-02-09 03:49:49 +00:00
namespace Friendica ;
2021-11-19 21:47:49 +00:00
use Friendica\App\Router ;
2021-11-14 19:28:36 +00:00
use Friendica\Capabilities\ICanHandleRequests ;
2021-11-21 20:52:36 +00:00
use Friendica\Capabilities\ICanCreateResponses ;
2021-11-20 14:38:03 +00:00
use Friendica\Core\Hook ;
2021-11-19 19:18:48 +00:00
use Friendica\Core\L10n ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2021-08-08 10:14:56 +00:00
use Friendica\Model\User ;
2021-11-21 19:06:36 +00:00
use Friendica\Module\Response ;
2021-11-20 14:38:03 +00:00
use Friendica\Module\Special\HTTPException as ModuleHTTPException ;
use Friendica\Network\HTTPException ;
2021-11-19 21:47:49 +00:00
use Friendica\Util\Profiler ;
2021-11-21 22:37:17 +00:00
use Psr\Http\Message\ResponseInterface ;
2021-11-19 21:47:49 +00:00
use Psr\Log\LoggerInterface ;
2018-10-17 19:30:41 +00:00
2018-02-09 03:49:49 +00:00
/**
* All modules in Friendica should extend BaseModule , although not all modules
* need to extend all the methods described here
*
2018-04-03 14:33:39 +00:00
* The filename of the module in src / Module needs to match the class name
* exactly to make the module available .
*
2018-09-15 23:28:38 +00:00
* @ author Hypolite Petovan < hypolite @ mrpetovan . com >
2018-02-09 03:49:49 +00:00
*/
2021-11-14 19:28:36 +00:00
abstract class BaseModule implements ICanHandleRequests
2018-02-09 03:49:49 +00:00
{
2021-11-14 19:28:36 +00:00
/** @var array */
2021-11-14 22:19:25 +00:00
protected $parameters = [];
2021-11-19 19:18:48 +00:00
/** @var L10n */
protected $l10n ;
2021-11-20 14:38:03 +00:00
/** @var App\BaseURL */
protected $baseUrl ;
/** @var App\Arguments */
protected $args ;
/** @var LoggerInterface */
protected $logger ;
/** @var Profiler */
protected $profiler ;
/** @var array */
protected $server ;
2021-11-21 20:52:36 +00:00
/** @var ICanCreateResponses */
2021-11-21 19:06:36 +00:00
protected $response ;
2021-11-19 19:18:48 +00:00
2021-11-21 19:06:36 +00:00
public function __construct ( L10n $l10n , App\BaseURL $baseUrl , App\Arguments $args , LoggerInterface $logger , Profiler $profiler , Response $response , array $server , array $parameters = [])
2018-02-09 03:49:49 +00:00
{
2021-11-17 20:32:57 +00:00
$this -> parameters = $parameters ;
2021-11-19 19:18:48 +00:00
$this -> l10n = $l10n ;
2021-11-20 14:38:03 +00:00
$this -> baseUrl = $baseUrl ;
$this -> args = $args ;
$this -> logger = $logger ;
$this -> profiler = $profiler ;
$this -> server = $server ;
2021-11-21 19:06:36 +00:00
$this -> response = $response ;
2018-09-30 20:47:28 +00:00
}
2018-02-09 03:49:49 +00:00
2021-11-18 20:33:05 +00:00
/**
2021-11-19 19:18:48 +00:00
* Wraps the L10n :: t () function for Modules
*
* @ see L10n :: t ()
*/
protected function t ( string $s , ... $args ) : string
{
2021-11-24 15:57:05 +00:00
return $this -> l10n -> t ( $s , ... $args );
2021-11-19 19:18:48 +00:00
}
/**
* Wraps the L10n :: tt () function for Modules
*
* @ see L10n :: tt ()
2021-11-18 20:33:05 +00:00
*/
2023-03-22 04:08:00 +00:00
protected function tt ( string $singular , string $plural , int $count ) : string
2021-11-18 20:33:05 +00:00
{
2023-03-22 04:08:00 +00:00
return $this -> l10n -> tt ( $singular , $plural , $count );
2021-11-18 20:33:05 +00:00
}
2018-09-30 20:47:28 +00:00
/**
2021-11-20 14:38:03 +00:00
* Module GET method to display raw content from technical endpoints
*
* Extend this method if the module is supposed to return communication data ,
* e . g . from protocol implementations .
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return void
2018-09-30 20:47:28 +00:00
*/
2021-11-20 14:38:03 +00:00
protected function rawContent ( array $request = [])
2018-09-30 20:47:28 +00:00
{
2019-11-05 22:01:45 +00:00
// echo '';
// exit;
2018-02-09 03:49:49 +00:00
}
/**
2021-11-20 14:38:03 +00:00
* Module GET method to display any content
*
* Extend this method if the module is supposed to return any display
* through a GET request . It can be an HTML page through templating or a
* XML feed or a JSON output .
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return string
2018-02-09 03:49:49 +00:00
*/
2021-11-20 14:38:03 +00:00
protected function content ( array $request = []) : string
2018-02-09 03:49:49 +00:00
{
2021-11-14 19:28:36 +00:00
return '' ;
2018-02-09 03:49:49 +00:00
}
2021-05-08 09:14:19 +00:00
/**
2021-11-20 14:38:03 +00:00
* Module DELETE method to process submitted data
*
* Extend this method if the module is supposed to process DELETE requests .
* Doesn ' t display any content
2021-11-28 12:44:42 +00:00
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return void
2021-05-08 09:14:19 +00:00
*/
2021-11-28 12:44:42 +00:00
protected function delete ( array $request = [])
2021-05-08 09:14:19 +00:00
{
}
/**
2021-11-20 14:38:03 +00:00
* Module PATCH method to process submitted data
*
* Extend this method if the module is supposed to process PATCH requests .
* Doesn ' t display any content
2021-11-28 12:44:42 +00:00
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return void
2021-05-08 09:14:19 +00:00
*/
2021-11-28 12:44:42 +00:00
protected function patch ( array $request = [])
2021-05-08 09:14:19 +00:00
{
}
2018-02-09 03:49:49 +00:00
/**
2021-11-20 14:38:03 +00:00
* Module POST method to process submitted data
*
* Extend this method if the module is supposed to process POST requests .
* Doesn ' t display any content
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return void
2018-02-09 03:49:49 +00:00
*/
2021-11-28 12:44:42 +00:00
protected function post ( array $request = [])
2018-02-09 03:49:49 +00:00
{
2021-11-20 14:38:03 +00:00
// $this->baseUrl->redirect('module');
2018-02-09 03:49:49 +00:00
}
2021-05-08 09:14:19 +00:00
/**
2021-11-20 14:38:03 +00:00
* Module PUT method to process submitted data
*
* Extend this method if the module is supposed to process PUT requests .
* Doesn ' t display any content
2021-11-28 12:44:42 +00:00
*
* @ param string [] $request The $_REQUEST content
2022-06-22 03:03:30 +00:00
* @ return void
2021-05-08 09:14:19 +00:00
*/
2021-11-28 12:44:42 +00:00
protected function put ( array $request = [])
2021-05-08 09:14:19 +00:00
{
}
2021-11-20 14:38:03 +00:00
/**
* { @ inheritDoc }
*/
2022-12-26 20:17:32 +00:00
public function run ( ModuleHTTPException $httpException , array $request = []) : ResponseInterface
2021-11-19 21:47:49 +00:00
{
// @see https://github.com/tootsuite/mastodon/blob/c3aef491d66aec743a3a53e934a494f653745b61/config/initializers/cors.rb
2022-01-02 21:17:04 +00:00
if ( substr ( $this -> args -> getQueryString (), 0 , 12 ) == '.well-known/' ) {
2021-11-21 21:23:35 +00:00
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Origin' );
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Headers' );
$this -> response -> setHeader ( Router :: GET , 'Access-Control-Allow-Methods' );
$this -> response -> setHeader ( 'false' , 'Access-Control-Allow-Credentials' );
2022-01-02 21:17:04 +00:00
} elseif ( substr ( $this -> args -> getQueryString (), 0 , 8 ) == 'profile/' ) {
2021-11-21 21:23:35 +00:00
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Origin' );
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Headers' );
$this -> response -> setHeader ( Router :: GET , 'Access-Control-Allow-Methods' );
$this -> response -> setHeader ( 'false' , 'Access-Control-Allow-Credentials' );
2022-01-02 21:17:04 +00:00
} elseif ( substr ( $this -> args -> getQueryString (), 0 , 4 ) == 'api/' ) {
2021-11-21 21:23:35 +00:00
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Origin' );
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Headers' );
$this -> response -> setHeader ( implode ( ',' , Router :: ALLOWED_METHODS ), 'Access-Control-Allow-Methods' );
$this -> response -> setHeader ( 'false' , 'Access-Control-Allow-Credentials' );
$this -> response -> setHeader ( 'Link' , 'Access-Control-Expose-Headers' );
2022-01-02 21:17:04 +00:00
} elseif ( substr ( $this -> args -> getQueryString (), 0 , 11 ) == 'oauth/token' ) {
2021-11-21 21:23:35 +00:00
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Origin' );
$this -> response -> setHeader ( '*' , 'Access-Control-Allow-Headers' );
$this -> response -> setHeader ( Router :: POST , 'Access-Control-Allow-Methods' );
$this -> response -> setHeader ( 'false' , 'Access-Control-Allow-Credentials' );
2021-11-19 21:47:49 +00:00
}
$placeholder = '' ;
2021-11-20 14:38:03 +00:00
$this -> profiler -> set ( microtime ( true ), 'ready' );
2021-11-19 21:47:49 +00:00
$timestamp = microtime ( true );
2021-11-20 14:38:03 +00:00
Core\Hook :: callAll ( $this -> args -> getModuleName () . '_mod_init' , $placeholder );
2021-11-19 21:47:49 +00:00
2021-11-20 14:38:03 +00:00
$this -> profiler -> set ( microtime ( true ) - $timestamp , 'init' );
2021-11-19 21:47:49 +00:00
2022-01-03 18:26:22 +00:00
switch ( $this -> args -> getMethod ()) {
2021-11-21 19:06:36 +00:00
case Router :: DELETE :
2021-11-28 12:44:42 +00:00
$this -> delete ( $request );
2021-11-21 19:06:36 +00:00
break ;
case Router :: PATCH :
2021-11-28 12:44:42 +00:00
$this -> patch ( $request );
2021-11-21 19:06:36 +00:00
break ;
case Router :: POST :
2021-11-28 12:44:42 +00:00
Core\Hook :: callAll ( $this -> args -> getModuleName () . '_mod_post' , $request );
$this -> post ( $request );
2021-11-21 19:06:36 +00:00
break ;
case Router :: PUT :
2021-11-28 12:44:42 +00:00
$this -> put ( $request );
2021-11-21 19:06:36 +00:00
break ;
2021-11-27 12:41:37 +00:00
}
$timestamp = microtime ( true );
// "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff.
$this -> rawContent ( $request );
try {
$arr = [ 'content' => '' ];
Hook :: callAll ( static :: class . '_mod_content' , $arr );
$this -> response -> addContent ( $arr [ 'content' ]);
2021-11-28 12:44:42 +00:00
$this -> response -> addContent ( $this -> content ( $request ));
2021-11-27 12:41:37 +00:00
} catch ( HTTPException $e ) {
2022-12-27 19:08:33 +00:00
// In case of System::externalRedirects(), we don't want to prettyprint the exception
// just redirect to the new location
if (( $e instanceof HTTPException\FoundException ) ||
( $e instanceof HTTPException\MovedPermanentlyException ) ||
( $e instanceof HTTPException\TemporaryRedirectException )) {
throw $e ;
}
2023-07-10 02:44:40 +00:00
$this -> response -> setStatus ( $e -> getCode (), $e -> getMessage ());
2022-12-26 20:17:32 +00:00
$this -> response -> addContent ( $httpException -> content ( $e ));
2021-11-27 12:41:37 +00:00
} finally {
$this -> profiler -> set ( microtime ( true ) - $timestamp , 'content' );
2021-11-19 21:47:49 +00:00
}
2021-11-21 22:37:17 +00:00
return $this -> response -> generate ();
2021-11-19 21:47:49 +00:00
}
2021-11-28 12:22:27 +00:00
/**
* Checks request inputs and sets default parameters
*
* @ param array $defaults Associative array of expected request keys and their default typed value . A null
* value will remove the request key from the resulting value array .
* @ param array $input Custom REQUEST array , superglobal instead
*
* @ return array Request data
*/
protected function checkDefaults ( array $defaults , array $input ) : array
{
$request = [];
foreach ( $defaults as $parameter => $defaultvalue ) {
2022-01-16 14:04:20 +00:00
$request [ $parameter ] = $this -> getRequestValue ( $input , $parameter , $defaultvalue );
2021-11-28 12:22:27 +00:00
}
foreach ( $input ? ? [] as $parameter => $value ) {
if ( $parameter == 'pagename' ) {
continue ;
}
if ( ! in_array ( $parameter , array_keys ( $defaults ))) {
$this -> logger -> notice ( 'Unhandled request field' , [ 'parameter' => $parameter , 'value' => $value , 'command' => $this -> args -> getCommand ()]);
}
}
$this -> logger -> debug ( 'Got request parameters' , [ 'request' => $request , 'command' => $this -> args -> getCommand ()]);
return $request ;
}
2022-01-16 14:04:20 +00:00
/**
* Fetch a request value and apply default values and check against minimal and maximal values
*
2022-06-22 14:13:46 +00:00
* @ param array $input Input fields
2022-06-22 03:03:30 +00:00
* @ param string $parameter Parameter
* @ param mixed $default Default
* @ param mixed $minimal_value Minimal value
* @ param mixed $maximum_value Maximum value
* @ return mixed null on error anything else on success ( ? )
2022-01-16 14:04:20 +00:00
*/
public function getRequestValue ( array $input , string $parameter , $default = null , $minimal_value = null , $maximum_value = null )
{
if ( is_string ( $default )) {
$value = ( string )( $input [ $parameter ] ? ? $default );
} elseif ( is_int ( $default )) {
$value = filter_var ( $input [ $parameter ] ? ? $default , FILTER_VALIDATE_INT );
if ( ! is_null ( $minimal_value )) {
$value = max ( filter_var ( $minimal_value , FILTER_VALIDATE_INT ), $value );
}
if ( ! is_null ( $maximum_value )) {
2022-01-16 15:22:35 +00:00
$value = min ( filter_var ( $maximum_value , FILTER_VALIDATE_INT ), $value );
2022-01-16 14:04:20 +00:00
}
} elseif ( is_float ( $default )) {
$value = filter_var ( $input [ $parameter ] ? ? $default , FILTER_VALIDATE_FLOAT );
if ( ! is_null ( $minimal_value )) {
$value = max ( filter_var ( $minimal_value , FILTER_VALIDATE_FLOAT ), $value );
}
if ( ! is_null ( $maximum_value )) {
2022-01-16 15:22:35 +00:00
$value = min ( filter_var ( $maximum_value , FILTER_VALIDATE_FLOAT ), $value );
2022-01-16 14:04:20 +00:00
}
} elseif ( is_array ( $default )) {
$value = filter_var ( $input [ $parameter ] ? ? $default , FILTER_DEFAULT , [ 'flags' => FILTER_FORCE_ARRAY ]);
} elseif ( is_bool ( $default )) {
$value = filter_var ( $input [ $parameter ] ? ? $default , FILTER_VALIDATE_BOOLEAN );
} elseif ( is_null ( $default )) {
$value = $input [ $parameter ] ? ? null ;
} else {
$this -> logger -> notice ( 'Unhandled default value type' , [ 'parameter' => $parameter , 'type' => gettype ( $default )]);
$value = null ;
}
return $value ;
}
2022-06-22 03:03:30 +00:00
/**
2018-10-17 19:30:41 +00:00
* Functions used to protect against Cross - Site Request Forgery
* The security token has to base on at least one value that an attacker can 't know - here it' s the session ID and the private key .
* In this implementation , a security token is reusable ( if the user submits a form , goes back and resubmits the form , maybe with small changes ;
2020-07-09 19:08:09 +00:00
* or if the security token is used for ajax - calls that happen several times ), but only valid for a certain amount of time ( 3 hours ) .
* The " typename " separates the security tokens of different types of forms . This could be relevant in the following case :
* A security token is used to protect a link from CSRF ( e . g . the " delete this profile " - link ) .
2018-10-17 19:30:41 +00:00
* If the new page contains by any chance external elements , then the used security token is exposed by the referrer .
2020-07-09 19:08:09 +00:00
* Actually , important actions should not be triggered by Links / GET - Requests at all , but sometimes they still are ,
2018-10-17 19:30:41 +00:00
* so this mechanism brings in some damage control ( the attacker would be able to forge a request to a form of this type , but not to forms of other types ) .
2022-06-22 03:03:30 +00:00
*
* @ param string $typename Type name
* @ return string Security hash with timestamp
2018-10-17 19:30:41 +00:00
*/
2022-06-22 03:03:30 +00:00
public static function getFormSecurityToken ( string $typename = '' ) : string
2018-10-17 19:30:41 +00:00
{
2021-11-21 19:06:36 +00:00
$user = User :: getById ( DI :: app () -> getLoggedInUserId (), [ 'guid' , 'prvkey' ]);
2018-10-17 19:30:41 +00:00
$timestamp = time ();
2021-11-21 19:06:36 +00:00
$sec_hash = hash ( 'whirlpool' , ( $user [ 'guid' ] ? ? '' ) . ( $user [ 'prvkey' ] ? ? '' ) . session_id () . $timestamp . $typename );
2018-10-17 19:30:41 +00:00
return $timestamp . '.' . $sec_hash ;
}
2022-06-16 14:35:39 +00:00
/**
* Checks if form ' s security ( CSRF ) token is valid .
*
* @ param string $typename ? ? ?
* @ param string $formname Name of form / field ( ? ? ? )
* @ return bool Whether it is valid
*/
public static function checkFormSecurityToken ( string $typename = '' , string $formname = 'form_security_token' ) : bool
2018-10-17 19:30:41 +00:00
{
$hash = null ;
if ( ! empty ( $_REQUEST [ $formname ])) {
/// @TODO Careful, not secured!
$hash = $_REQUEST [ $formname ];
}
if ( ! empty ( $_SERVER [ 'HTTP_X_CSRF_TOKEN' ])) {
/// @TODO Careful, not secured!
$hash = $_SERVER [ 'HTTP_X_CSRF_TOKEN' ];
}
if ( empty ( $hash )) {
return false ;
}
$max_livetime = 10800 ; // 3 hours
2021-08-09 20:33:46 +00:00
$user = User :: getById ( DI :: app () -> getLoggedInUserId (), [ 'guid' , 'prvkey' ]);
2018-10-17 19:30:41 +00:00
$x = explode ( '.' , $hash );
2019-10-07 18:10:30 +00:00
if ( time () > ( intval ( $x [ 0 ]) + $max_livetime )) {
2018-10-17 19:30:41 +00:00
return false ;
}
2021-08-08 10:14:56 +00:00
$sec_hash = hash ( 'whirlpool' , ( $user [ 'guid' ] ? ? '' ) . ( $user [ 'prvkey' ] ? ? '' ) . session_id () . $x [ 0 ] . $typename );
2018-10-17 19:30:41 +00:00
return ( $sec_hash == $x [ 1 ]);
}
2022-06-16 14:35:39 +00:00
public static function getFormSecurityStandardErrorMessage () : string
2018-10-17 19:30:41 +00:00
{
2022-10-17 21:11:00 +00:00
return DI :: l10n () -> t ( " The form security token was not correct. This probably happened because the form has been opened for too long \x28 >3 hours \x29 before submitting it. " );
2018-10-17 19:30:41 +00:00
}
2022-06-16 14:35:39 +00:00
public static function checkFormSecurityTokenRedirectOnError ( string $err_redirect , string $typename = '' , string $formname = 'form_security_token' )
2018-10-17 19:30:41 +00:00
{
if ( ! self :: checkFormSecurityToken ( $typename , $formname )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'checkFormSecurityToken failed: user ' . DI :: app () -> getLoggedInUserNickname () . ' - form element ' . $typename );
Logger :: debug ( 'checkFormSecurityToken failed' , [ 'request' => $_REQUEST ]);
2022-10-17 18:55:22 +00:00
DI :: sysmsg () -> addNotice ( self :: getFormSecurityStandardErrorMessage ());
2019-12-15 23:28:31 +00:00
DI :: baseUrl () -> redirect ( $err_redirect );
2018-10-17 19:30:41 +00:00
}
}
2022-06-16 14:35:39 +00:00
public static function checkFormSecurityTokenForbiddenOnError ( string $typename = '' , string $formname = 'form_security_token' )
2018-10-17 19:30:41 +00:00
{
if ( ! self :: checkFormSecurityToken ( $typename , $formname )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'checkFormSecurityToken failed: user ' . DI :: app () -> getLoggedInUserNickname () . ' - form element ' . $typename );
Logger :: debug ( 'checkFormSecurityToken failed' , [ 'request' => $_REQUEST ]);
2018-12-26 05:40:12 +00:00
2019-05-02 03:16:10 +00:00
throw new \Friendica\Network\HTTPException\ForbiddenException ();
2018-10-17 19:30:41 +00:00
}
}
2020-08-06 14:34:11 +00:00
2022-06-22 03:03:30 +00:00
protected static function getContactFilterTabs ( string $baseUrl , string $current , bool $displayCommonTab ) : array
2020-08-06 14:34:11 +00:00
{
$tabs = [
[
'label' => DI :: l10n () -> t ( 'All contacts' ),
'url' => $baseUrl . '/contacts' ,
'sel' => ! $current || $current == 'all' ? 'active' : '' ,
],
[
'label' => DI :: l10n () -> t ( 'Followers' ),
'url' => $baseUrl . '/contacts/followers' ,
'sel' => $current == 'followers' ? 'active' : '' ,
],
[
'label' => DI :: l10n () -> t ( 'Following' ),
'url' => $baseUrl . '/contacts/following' ,
'sel' => $current == 'following' ? 'active' : '' ,
],
[
'label' => DI :: l10n () -> t ( 'Mutual friends' ),
'url' => $baseUrl . '/contacts/mutuals' ,
'sel' => $current == 'mutuals' ? 'active' : '' ,
],
];
if ( $displayCommonTab ) {
$tabs [] = [
'label' => DI :: l10n () -> t ( 'Common' ),
'url' => $baseUrl . '/contacts/common' ,
'sel' => $current == 'common' ? 'active' : '' ,
];
}
return $tabs ;
}
2018-02-09 03:49:49 +00:00
}