diff --git a/system/typemill/Controllers/ControllerApiGlobals.php b/system/typemill/Controllers/ControllerApiGlobals.php index b66f4ac..a154450 100644 --- a/system/typemill/Controllers/ControllerApiGlobals.php +++ b/system/typemill/Controllers/ControllerApiGlobals.php @@ -12,10 +12,11 @@ class ControllerApiGlobals extends Controller { $navigation = new Navigation(); $systemNavigation = $navigation->getSystemNavigation( - $userrole = $request->getAttribute('c_userrole'), - $acl = $this->c->get('acl'), - $urlinfo = $this->c->get('urlinfo'), - $dispatcher = $this->c->get('dispatcher') + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser ); # won't work because api has no session, instead you have to pass user diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 1851e43..4065880 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -101,7 +101,7 @@ class ControllerWebAuth extends Controller # generate new authcode $authcodevalue = rand(10000, 99999); - $mail = new SimpleMail($settings); + $mail = new SimpleMail($this->settings); $subject = Translations::translate('Your authentication code for Typemill'); $message = Translations::translate('Use the following authentication code to login into Typemill') . ': ' . $authcodevalue; diff --git a/system/typemill/Middleware/ApiAuthentication.php b/system/typemill/Middleware/ApiAuthentication.php index c38c2b5..7f7c3c1 100644 --- a/system/typemill/Middleware/ApiAuthentication.php +++ b/system/typemill/Middleware/ApiAuthentication.php @@ -63,6 +63,9 @@ class ApiAuthentication } */ + +########### WHY NOT USE BASIC AUTH PARAMS FROM URI ? + $params = []; if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches)) diff --git a/system/typemill/Middleware/CustomHeadersMiddleware.php b/system/typemill/Middleware/CustomHeadersMiddleware.php index 185cae6..9a19135 100644 --- a/system/typemill/Middleware/CustomHeadersMiddleware.php +++ b/system/typemill/Middleware/CustomHeadersMiddleware.php @@ -10,23 +10,33 @@ use Slim\Psr7\Response; class CustomHeadersMiddleware implements MiddlewareInterface { protected $settings; + + protected $urlinfo; - public function __construct($settings) + public function __construct($settings, $urlinfo) { $this->settings = $settings; + + $this->urlinfo = $urlinfo; } public function process(Request $request, RequestHandler $handler) :response { $scheme = $request->getUri()->getScheme(); + # add the custom headers to the response after everything is processed $response = $handler->handle($request); $response = $response->withoutHeader('Server'); $response = $response->withHeader('X-Powered-By', 'Typemill'); $headersOff = $this->settings['headersoff'] ?? false; + + ################### + # SECURITY HEADER # + ################### + if(!$headersOff) { $response = $response @@ -41,6 +51,38 @@ class CustomHeadersMiddleware implements MiddlewareInterface } } + ################### + # CORS HEADER # + ################### + + $origin = $request->getHeaderLine('Origin'); + $corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false; + $whitelist = []; + + if($corsdomains && $corsdomains != '') + { + $corsdomains = explode(",", $corsdomains); + foreach($corsdomains as $domain) + { + $domain = trim($domain); + if($domain != '') + { + $whitelist[] = $domain; + } + } + } + + if(!$origin OR $origin == '' OR !isset($whitelist[$origin])) + { + # set current website as default origin and block all cross origin calls + $origin = $this->urlinfo['baseurl']; + } + + $response = $response->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + ->withHeader('Access-Control-Allow-Credentials', 'true'); + return $response; } } \ No newline at end of file diff --git a/system/typemill/Middleware/RemoveCredentialsMiddleware.php b/system/typemill/Middleware/RemoveCredentialsMiddleware.php new file mode 100644 index 0000000..a0fe634 --- /dev/null +++ b/system/typemill/Middleware/RemoveCredentialsMiddleware.php @@ -0,0 +1,28 @@ +getUri(); + + # Remove user information (username:password) from the URI + $uri = $uri->withUserInfo(''); + + # Create a new request with the modified URI + $request = $request->withUri($uri); + + # we could add basic auth credentials to request for later usage + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/SessionMiddleware.php b/system/typemill/Middleware/SessionMiddleware.php index bb21251..14afca3 100644 --- a/system/typemill/Middleware/SessionMiddleware.php +++ b/system/typemill/Middleware/SessionMiddleware.php @@ -11,54 +11,52 @@ use Typemill\Models\User; class SessionMiddleware implements MiddlewareInterface { - protected $segments; + protected $segments; - protected $route; + protected $route; + + public function __construct($segments, $route) + { + $this->segments = $segments; - protected $uri; - - public function __construct($segments, $route, $uri) - { - $this->segments = $segments; - - $this->route = $route; - - $this->uri = $uri; - } - + $this->route = $route; + } + public function process(Request $request, RequestHandler $handler) :response - { - $scheme = $request->getUri()->getScheme(); - - # start session - Session::startSessionForSegments($this->segments, $this->route, $scheme); + { + $uri = $request->getUri(); - $authenticated = ( - (isset($_SESSION['username'])) && - (isset($_SESSION['login'])) - ) - ? true : false; + $scheme = $request->getUri()->getScheme(); + + # start session + Session::startSessionForSegments($this->segments, $this->route, $scheme); - if($authenticated) - { - # add userdata to the request for later use - $user = new User(); + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; - if($user->setUser($_SESSION['username'])) - { - $userdata = $user->getUserData(); + if($authenticated) + { + # add userdata to the request for later use + $user = new User(); - $request = $request->withAttribute('c_username', $userdata['username']); - $request = $request->withAttribute('c_userrole', $userdata['userrole']); - if(isset($userdata['darkmode'])) - { - $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); - } - } - } + if($user->setUser($_SESSION['username'])) + { + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + if(isset($userdata['darkmode'])) + { + $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); + } + } + } $response = $handler->handle($request); return $response; - } + } } \ No newline at end of file diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php index 4adb2c4..564d85b 100644 --- a/system/typemill/Static/Helpers.php +++ b/system/typemill/Static/Helpers.php @@ -8,6 +8,8 @@ class Helpers{ public static function urlInfo($uri) { + $uri = $uri->withUserInfo(''); + $basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); $currentpath = $uri->getPath(); $route = str_replace($basepath, '', $currentpath); diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index aa9ed44..cdce570 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -240,4 +240,9 @@ fieldsetdeveloper: headersoff: type: checkbox label: Disable Custom Headers - checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. \ No newline at end of file + checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. + corsdomains: + type: textarea + label: Allowed Domains for API-Access (CORS) + placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' + description: Add all domains separated with comma, that should have access to the API. Domains will be added to the cors-header. \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 2fa40c0..3319999 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -21,6 +21,7 @@ use Typemill\Events\OnPluginsLoaded; use Typemill\Events\OnSessionSegmentsLoaded; use Typemill\Events\OnRolesPermissionsLoaded; use Typemill\Events\OnResourcesLoaded; +use Typemill\Middleware\RemoveCredentialsMiddleware; use Typemill\Middleware\SessionMiddleware; use Typemill\Middleware\OldInputMiddleware; use Typemill\Middleware\ValidationErrorsMiddleware; @@ -75,6 +76,23 @@ $uriFactory = new UriFactory(); $uri = $uriFactory->createFromGlobals($_SERVER); $urlinfo = Helpers::urlInfo($uri); +/* PROBLEM WITH URLINFO + +* it contains basic authentication like + + ["basepath"]=> "/typemill" + ["currentpath"]=> "/typemill/api/v1/mainnavi" + ["route"]=> "/api/v1/mainnavi" + ["scheme"]=> "http" + ["authority"]=> "trendschau:password@localhost" + ["protocol"]=> "http://trendschau:password@localhost" + ["baseurl"] => "http://trendschau:password@localhost/typemill" + ["currenturl"]=> "http://trendschau:password@localhost/typemill/api/v1/mainnavi" + +* It probably contains wrong scheme when used with proxy + +*/ + $timer['settings'] = microtime(true); /**************************** @@ -305,7 +323,7 @@ foreach($middleware as $pluginMiddleware) } } -$app->add(new CustomHeadersMiddleware($settings)); +$app->add(new CustomHeadersMiddleware($settings, $urlinfo)); $app->add(new AssetMiddleware($assets, $container->get('view'))); @@ -346,7 +364,9 @@ $errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($reque $app->add($errorMiddleware); -$app->add(new SessionMiddleware($session_segments, $urlinfo['route'], $uri)); +$app->add(new SessionMiddleware($session_segments, $urlinfo['route'])); + +$app->add(new RemoveCredentialsMiddleware()); if(isset($settings['proxy']) && $settings['proxy']) {