From 9863855637f46982213b2877421c0cc140ca5280 Mon Sep 17 00:00:00 2001 From: trendschau Date: Tue, 11 Feb 2025 22:48:45 +0100 Subject: [PATCH] Added trusted ips and trusted hosts for api --- .../typemill/Middleware/ApiAuthentication.php | 49 +++++++++++++++---- .../Middleware/CorsHeadersMiddleware.php | 3 +- system/typemill/routes/api.php | 6 +-- system/typemill/settings/system.yaml | 18 +++++-- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/system/typemill/Middleware/ApiAuthentication.php b/system/typemill/Middleware/ApiAuthentication.php index 7f7c3c1..1b7529e 100644 --- a/system/typemill/Middleware/ApiAuthentication.php +++ b/system/typemill/Middleware/ApiAuthentication.php @@ -11,6 +11,13 @@ use Typemill\Static\Session; class ApiAuthentication { + protected $settings; + + public function __construct($settings) + { + $this->settings = $settings; + } + public function __invoke(Request $request, RequestHandler $handler) { $routeContext = RouteContext::fromRequest($request); @@ -26,18 +33,45 @@ class ApiAuthentication return $response; } - else - { - # return error message - } } - # api authentication with basic auth - # inspired by tuupola + # api authentication with basic auth, inspired by tuupola + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; $host = $request->getUri()->getHost(); $scheme = $request->getUri()->getScheme(); $server_params = $request->getServerParams(); + $apiaccess = true; + if(isset($this->settings['trustedipsforapi']) && $this->settings['trustedipsforapi'] !== '') + { + $trustedIPs = array_map('trim', explode(',', $this->settings['trustedipsforapi'])); + + if(!in_array($ipAddress, $trustedIPs)) + { + $apiaccess = false; + } + } + if(isset($this->settings['trustedhostsforapi']) && $this->settings['trustedhostsforapi'] !== '') + { + $trustedHosts = array_map('trim', explode(',', $this->settings['trustedhostsforapi'])); + + if(!in_array($host, $trustedHosts)) + { + $apiaccess = false; + } + } + + if(!$apiaccess) + { + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => 'Not trusted.' + ])); + + return $response->withHeader('WWW-Authenticate', 'Basic realm=')->withStatus(401); + } + /* # HTTP allowed only if secure is false or server is in relaxed array. # use own logic for https proto forwarding @@ -63,9 +97,6 @@ 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/CorsHeadersMiddleware.php b/system/typemill/Middleware/CorsHeadersMiddleware.php index 062db4a..fe47f5f 100644 --- a/system/typemill/Middleware/CorsHeadersMiddleware.php +++ b/system/typemill/Middleware/CorsHeadersMiddleware.php @@ -21,7 +21,8 @@ class CorsHeadersMiddleware implements MiddlewareInterface } public function process(Request $request, RequestHandler $handler) :response - { + { + # not in use right now, see here: https://chatgpt.com/c/67aba713-a7b0-8005-993e-a43ec4add72a # add the custom headers to the response after everything is processed $response = $handler->handle($request); diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php index 6951252..f25e0ba 100644 --- a/system/typemill/routes/api.php +++ b/system/typemill/routes/api.php @@ -102,7 +102,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { $group->get('/article/content', ControllerApiGlobals::class . ':getArticleContent')->setName('api.articlecontent.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author $group->get('/article/meta', ControllerApiGlobals::class . ':getArticleMeta')->setName('api.articlemeta.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author -})->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication()); +})->add(new ApiAuthentication($settings)); # api-routes from plugins if(isset($routes['api']) && !empty($routes['api'])) @@ -119,12 +119,12 @@ if(isset($routes['api']) && !empty($routes['api'])) if($resources && $privilege) { # protected api requires authentication and authorization - $app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication()); + $app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new ApiAuthentication($settings)); } else { # public api routes - $app->{$method}($route, $class)->setName($name)->add(new CorsHeadersMiddleware($settings, $urlinfo)); + $app->{$method}($route, $class)->setName($name); } } } \ No newline at end of file diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 21816b0..93a1302 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -287,11 +287,19 @@ fieldsetdeveloper: label: "Allowed Domains for Content on Typemill (CSP-Headers)" placeholder: 'https://www.google.com,*google.com' description: "List all domains, separated by commas, to allow content integration, such as iframes, on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." - corsdomains: - type: textarea - label: "Allowed Domains for API-Access (CORS-Headers)" - placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' - description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header." +# corsdomains: +# type: textarea +# label: "Allowed Domains for API-Access (CORS-Headers)" +# placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' +# description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header." + trustedipsforapi: + type: text + label: "Trusted IPs for api calls (comma separated)" + placeholder: "192.168.1.1, 203.0.113.0" + trustedhostsforapi: + type: text + label: "Trusted Hosts for api calls (comma separated)" + placeholder: "mydomain.org,sub.mydomain.org" loginlink: type: checkbox label: "Login with link"