diff --git a/CHANGELOG b/CHANGELOG index 13405a3ec..94d763c4e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -348,6 +348,8 @@ -------------------------------------------------------------------------------- Changes in version 5.1.43 -------------------------------------------------------------------------------- +- add Slim middleware for Basic authentication (can be used by extensions) +- rest api endpoint 'statstotal' returns number of groups and categories -------------------------------------------------------------------------------- Changes in version 5.1.42 diff --git a/inc/inc.ClassAuthenticationMiddleware.php b/inc/inc.ClassAuthenticationMiddleware.php index 97e74c55f..f406747cf 100644 --- a/inc/inc.ClassAuthenticationMiddleware.php +++ b/inc/inc.ClassAuthenticationMiddleware.php @@ -35,8 +35,11 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ private $container; - public function __construct($container) { + private $responsefactory; + + public function __construct($container, $responsefactory) { $this->container = $container; + $this->responsefactory = $responsefactory; } /** @@ -63,7 +66,7 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ return $response; } - $logger->log("Invoke middleware for method " . $request->getMethod() . " on '" . $request->getUri()->getPath() . "'", PEAR_LOG_INFO); + $logger->log("Invoke AuthSessionMiddleware for method " . $request->getMethod() . " on '" . $request->getUri()->getPath() . "'", PEAR_LOG_INFO); require_once("inc/inc.ClassSession.php"); $session = new SeedDMS_Session($dms->getDb()); if (isset($_COOKIE["mydms_session"])) { @@ -73,6 +76,7 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ /* Delete Cookie */ setcookie("mydms_session", $dms_session, time() - 3600, $settings->_httpRoot); $logger->log("Session for id '" . $dms_session . "' has gone", PEAR_LOG_ERR); + $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } @@ -83,6 +87,7 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ setcookie("mydms_session", $dms_session, time() - 3600, $settings->_httpRoot); if ($settings->_enableGuestLogin) { if (!($userobj = $dms->getUser($settings->_guestID))) { + $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } } else { @@ -92,12 +97,14 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ if ($userobj->isAdmin()) { if ($resArr["su"]) { if (!($userobj = $dms->getUser($resArr["su"]))) { + $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } } } $dms->setUser($userobj); } else { + $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } $this->container->set('userobj', $userobj); @@ -106,3 +113,174 @@ class SeedDMS_Auth_Middleware_Session { /* {{{ */ return $response; } } /* }}} */ + +/** + * Middleware for authentication based on basic authentication + * + **/ +class SeedDMS_Auth_Middleware_Basic { /* {{{ */ + + private $container; + + private $responsefactory; + + public function __construct($container, $responsefactory) { + $this->container = $container; + $this->responsefactory = $responsefactory; + } + + /** + * Basic authentication middleware invokable class + * + * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request + * @param \Psr\Http\Message\ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke($request, $handler) { + $dms = $this->container->get('dms'); + $settings = $this->container->get('config'); + $logger = $this->container->get('logger'); + $userobj = null; + if ($this->container->has('userobj')) { + $userobj = $this->container->get('userobj'); + } + + if ($userobj) { + $response = $handler->handle($request); + return $response; + } + + $logger->log("Invoke AuthBasicMiddleware for method " . $request->getMethod() . " on '" . $request->getUri()->getPath() . "'", PEAR_LOG_INFO); + $environment = $request->getServerParams(); + if(!empty($environment['HTTP_AUTHORIZATION'])) { + $tmp = explode(' ', $environment['HTTP_AUTHORIZATION'], 2); + switch($tmp[0]) { + case 'Basic': + $logger->log("Basic authentication with ".$tmp[0]."=".$tmp[1], PEAR_LOG_INFO); + $authenticator = $this->container->get('authenticator'); + $kk = explode(':', base64_decode($tmp[1])); + $userobj = $authenticator->authenticate($kk[0], $kk[1]); + if(!$userobj) { + $logger->log("Login with basic authentication for '".$kk[0]."' failed", PEAR_LOG_ERR); + $response = $this->responsefactory->createResponse(); + return $response->withStatus(403); + } + $dms->setUser($userobj); + if($this->container instanceof \Slim\Container) + $this->container['userobj'] = $userobj; + else + $this->container->set('userobj', $userobj); + $logger->log("Login with basic authentication as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); + break; + } + } + $this->container->set('userobj', $userobj); + + if(!$userobj) + $logger->log("Not yet authenticated. Pass on to next middleware", PEAR_LOG_INFO); + else + $logger->log("Authenticated as ".(is_object($userobj) ? $userobj->getLogin() : "annon").". Pass on to next middleware", PEAR_LOG_INFO); + + /* Always pass on to the next middleware. If that middleware does + * authentication, then it should first check if 'userobj' in the container + * is already set. The authentication shipped with seeddms restapi does that + * and skips its own authentication, if userobj already exists. + */ + $response = $handler->handle($request); + return $response; + } +} /* }}} */ + +/** + * Middleware for authentication based on token + * + **/ +class SeedDMS_Auth_Middleware_Token { /* {{{ */ + + private $container; + + private $responsefactory; + + public function __construct($container, $responsefactory) { + $this->container = $container; + $this->responsefactory = $responsefactory; + } + + /** + * Basic authentication middleware invokable class + * + * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request + * @param \Psr\Http\Message\ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke($request, $handler) { + $dms = $this->container->get('dms'); + $settings = $this->container->get('config'); + $logger = $this->container->get('logger'); + $userobj = null; + if ($this->container->has('userobj')) { + $userobj = $this->container->get('userobj'); + } + + if ($userobj) { + $response = $handler->handle($request); + return $response; + } + + $logger->log("Invoke AuthTokenMiddleware for method " . $request->getMethod() . " on '" . $request->getUri()->getPath() . "'", PEAR_LOG_INFO); + $environment = $request->getServerParams(); + /* Do not even try to authenticate if HTTP_AUTHORIZATION is empty, contains + * a ' ' (in case of Basic authentication), the api key is not set, the api + * user is not set. + */ + if (!empty($environment['HTTP_AUTHORIZATION']) && strstr($environment['HTTP_AUTHORIZATION'], ' ') === false && !empty($settings->_apiKey) && !empty($settings->_apiUserId)) { + $logger->log("Authorization key: ".$environment['HTTP_AUTHORIZATION'], PEAR_LOG_DEBUG); + if($settings->_apiKey == $environment['HTTP_AUTHORIZATION']) { + if(!($userobj = $dms->getUser($settings->_apiUserId))) { + $response = $this->responsefactory->createResponse(); + $response = $response->withHeader('Content-Type', 'application/json'); + $response = $response->withStatus(403); + $response->getBody()->write( + (string)json_encode( + ['success'=>false, 'message'=>'Invalid user associated with api key', 'data'=>''], + JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR + ) + ); + return $response; + } + } else { + $response = $this->responsefactory->createResponse(); + $response = $response->withHeader('Content-Type', 'application/json'); + $response = $response->withStatus(403); + $response->getBody()->write( + (string)json_encode( + ['success'=>false, 'message'=>'Wrong api key', 'data'=>''], + JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR + ) + ); + return $response; + } + $logger->log("Login with apikey as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); + } + + $this->container->set('userobj', $userobj); + + if(!$userobj) + $logger->log("Not yet authenticated. Pass on to next middleware", PEAR_LOG_INFO); + else + $logger->log("Authenticated as ".(is_object($userobj) ? $userobj->getLogin() : "annon").". Pass on to next middleware", PEAR_LOG_INFO); + + /* Always pass on to the next middleware. If that middleware does + * authentication, then it should first check if 'userobj' in the container + * is already set. The authentication shipped with seeddms restapi does that + * and skips its own authentication, if userobj already exists. + */ + $response = $handler->handle($request); + return $response; + } +} /* }}} */ + diff --git a/restapi/index.php b/restapi/index.php index 514249486..898a021a5 100644 --- a/restapi/index.php +++ b/restapi/index.php @@ -3098,7 +3098,7 @@ final class SeedDMS_RestapiController { /* {{{ */ return $check; $data = []; - foreach (array('docstotal', 'folderstotal', 'userstotal') as $type) { + foreach (array('docstotal', 'folderstotal', 'userstotal', 'groupstotal', 'categoriestotal') as $type) { $total = $dms->getStatisticalData($type); $data[$type] = $total; } @@ -3167,7 +3167,12 @@ class RestapiAuthMiddleware implements MiddlewareInterface { /* {{{ */ } /** - * Example middleware invokable class + * Auth middleware invokable class + * + * This methods checks for an api token in the Authorization header or + * a valid session in the cookie `mydms_session`. + * It does not support Basic authentication. It actually treats that + * as a wrong api token and authentication fails. * * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request * @param \Psr\Http\Server\RequestHandlerInterface $handler @@ -3229,6 +3234,8 @@ class RestapiAuthMiddleware implements MiddlewareInterface { /* {{{ */ $userobj = null; // $logger->log(var_export($environment, true), PEAR_LOG_DEBUG); if(!empty($environment['HTTP_AUTHORIZATION']) && !empty($settings->_apiKey) && !empty($settings->_apiUserId)) { + /* We cannot handle Basic authentication, so skip it */ + if (substr($environment['HTTP_AUTHORIZATION'], 0, 6) != 'Basic ') { $logger->log("Authorization key: ".$environment['HTTP_AUTHORIZATION'], PEAR_LOG_DEBUG); if($settings->_apiKey == $environment['HTTP_AUTHORIZATION']) { if(!($userobj = $dms->getUser($settings->_apiUserId))) { @@ -3256,6 +3263,9 @@ class RestapiAuthMiddleware implements MiddlewareInterface { /* {{{ */ return $response; } $logger->log("Login with apikey as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); + } else { + $logger->log("Login with Basic auth cannot be handled by AuthMiddleware", PEAR_LOG_INFO); + } } else { $logger->log("Checking for valid session", PEAR_LOG_INFO); require_once("../inc/inc.ClassSession.php");