Browse Source

Merge branch 'dev-php-7.4' into reactjs

Roman Hergenreder 3 years ago
parent
commit
11323f2781

+ 1 - 0
core/Api/ApiKey/Create.class.php

@@ -9,6 +9,7 @@ class Create extends Request {
   public function __construct($user, $externalCall = false) {
     parent::__construct($user, $externalCall, array());
     $this->apiKeyAllowed = false;
+    $this->csrfTokenRequired = true;
     $this->loginRequired = true;
   }
 

+ 1 - 0
core/Api/ApiKey/Fetch.class.php

@@ -12,6 +12,7 @@ class Fetch extends Request {
   public function __construct($user, $externalCall = false) {
     parent::__construct($user, $externalCall, array());
     $this->loginRequired = true;
+    $this->csrfTokenRequired = true;
   }
 
   public function execute($values = array()) {

+ 1 - 0
core/Api/ApiKey/Refresh.class.php

@@ -13,6 +13,7 @@ class Refresh extends Request {
       "id" => new Parameter("id", Parameter::TYPE_INT),
     ));
     $this->loginRequired = true;
+    $this->csrfTokenRequired = true;
   }
 
   private function apiKeyExists() {

+ 1 - 0
core/Api/ApiKey/Revoke.class.php

@@ -13,6 +13,7 @@ class Revoke extends Request {
       "id" => new Parameter("id", Parameter::TYPE_INT),
     ));
     $this->loginRequired = true;
+    $this->csrfTokenRequired = true;
   }
 
   private function apiKeyExists() {

+ 1 - 0
core/Api/Notifications/Create.class.php

@@ -17,6 +17,7 @@ class Create extends Request {
       'message' =>  new StringType('message', 256),
     ));
     $this->isPublic = false;
+    $this->csrfTokenRequired = true;
   }
 
   private function checkUser($userId) {

+ 1 - 0
core/Api/Notifications/Fetch.class.php

@@ -12,6 +12,7 @@ class Fetch extends Request {
   public function __construct($user, $externalCall = false) {
     parent::__construct($user, $externalCall, array());
     $this->loginRequired = true;
+    $this->csrfTokenRequired = true;
   }
 
   private function fetchUserNotifications() {

+ 13 - 3
core/Api/Request.class.php

@@ -17,6 +17,7 @@ class Request {
   protected bool $isDisabled;
   protected bool $apiKeyAllowed;
   protected int $requiredGroup;
+  protected bool $csrfTokenRequired;
 
   private array $aDefaultParams;
   private array $allowedMethods;
@@ -37,6 +38,7 @@ class Request {
     $this->allowedMethods = array("GET", "POST");
     $this->requiredGroup = 0;
     $this->lastError = "";
+    $this->csrfTokenRequired = false;
   }
 
   protected function forbidMethod($method) {
@@ -111,13 +113,13 @@ class Request {
     }
 
     if($this->loginRequired || $this->requiredGroup > 0) {
-      $authorized = false;
+      $apiKeyAuthorized = false;
       if(isset($values['api_key']) && $this->apiKeyAllowed) {
         $apiKey = $values['api_key'];
-        $authorized = $this->user->authorize($apiKey);
+        $apiKeyAuthorized = $this->user->authorize($apiKey);
       }
 
-      if(!$this->user->isLoggedIn() && !$authorized) {
+      if(!$this->user->isLoggedIn() && !$apiKeyAuthorized) {
         $this->lastError = 'You are not logged in.';
         header('HTTP 1.1 401 Unauthorized');
         return false;
@@ -125,6 +127,14 @@ class Request {
         $this->lastError = "Insufficient permissions. Required group: ". GroupName($this->requiredGroup);
         header('HTTP 1.1 401 Unauthorized');
         return false;
+      } else if($this->csrfTokenRequired && !$apiKeyAuthorized && $this->externalCall) {
+        // csrf token required + external call
+        // if it's not a call with API_KEY, check for csrf_token
+        if (!isset($values["csrf_token"]) || strcmp($values["csrf_token"], $this->user->getSession()->getCsrfToken()) !== 0) {
+          $this->lastError = "CSRF-Token mismatch";
+          header('HTTP 1.1 403 Forbidden');
+          return false;
+        }
       }
     }
 

+ 1 - 0
core/Api/SetLanguage.class.php

@@ -17,6 +17,7 @@ class SetLanguage extends Request {
       'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
       'langCode' => new StringType('langCode', 5, true, NULL),
     ));
+    $this->csrfTokenRequired = true;
   }
 
   private function checkLanguage() {

+ 1 - 0
core/Api/User/Fetch.class.php

@@ -20,6 +20,7 @@ class Fetch extends Request {
     $this->loginRequired = true;
     $this->requiredGroup = USER_GROUP_ADMIN;
     $this->userCount = 0;
+    $this->csrfTokenRequired = true;
   }
 
   private function getUserCount() {

+ 28 - 0
core/Api/User/Info.class.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Api\User;
+
+use \Api\Request;
+
+class Info extends Request {
+
+  public function __construct($user, $externalCall = false) {
+    parent::__construct($user, $externalCall, array());
+    $this->csrfTokenRequired = true;
+  }
+
+  public function execute($values = array()) {
+    if(!parent::execute($values)) {
+      return false;
+    }
+
+    if (!$this->user->isLoggedIn()) {
+      $this->result["loggedIn"] = false;
+    } else {
+      $this->result["loggedIn"] = true;
+    }
+
+    $this->result["user"] = $this->user->jsonSerialize();
+    return $this->success;
+  }
+}

+ 1 - 0
core/Api/User/Login.class.php

@@ -65,6 +65,7 @@ class Login extends Request {
           if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) {
             return $this->createError("Error creating Session: " . $sql->getLastError());
           } else {
+            $this->result["loggedIn"] = true;
             $this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
             $this->success = true;
           }

+ 1 - 0
core/Api/User/Logout.class.php

@@ -10,6 +10,7 @@ class Logout extends Request {
     parent::__construct($user, $externalCall);
     $this->loginRequired = true;
     $this->apiKeyAllowed = false;
+    $this->csrfTokenRequired = true;
   }
 
   public function execute($values = array()) {

+ 1 - 0
core/Configuration/CreateDatabase.class.php

@@ -47,6 +47,7 @@ class CreateDatabase {
       ->addString("browser", 64)
       ->addJson("data", false, '{}')
       ->addBool("stay_logged_in", true)
+      ->addString("csrf_token", 16 )
       ->primaryKey("uid", "user_id")
       ->foreignKey("user_id", "User", "uid", new CascadeStrategy());
 

+ 0 - 1
core/Elements/View.class.php

@@ -21,7 +21,6 @@ abstract class View extends StaticView {
   }
 
   public function getTitle() { return $this->title; }
-  public function __toString() { return $this->getCode(); }
   public function getDocument() { return $this->document; }
   public function isSearchable() { return $this->searchable; }
   public function getReference() { return $this->reference; }

+ 13 - 4
core/Objects/Session.class.php

@@ -19,15 +19,17 @@ class Session extends ApiObject {
   private ?string $os;
   private ?string $browser;
   private bool $stayLoggedIn;
+  private string $csrfToken;
 
-  public function __construct(User $user, ?int $sessionId) {
+  public function __construct(User $user, ?int $sessionId, ?string $csrfToken) {
     $this->user = $user;
     $this->sessionId = $sessionId;
     $this->stayLoggedIn = true;
+    $this->csrfToken = $csrfToken ?? generateRandomString(16);
   }
 
   public static function create($user, $stayLoggedIn) {
-    $session = new Session($user, null);
+    $session = new Session($user, null, null);
     if($session->insert($stayLoggedIn)) {
       return $session;
     }
@@ -85,6 +87,7 @@ class Session extends ApiObject {
       'ipAddress' => $this->ipAddress,
       'os' => $this->os,
       'browser' => $this->browser,
+      'csrf_token' => $this->csrfToken
     );
   }
 
@@ -93,7 +96,7 @@ class Session extends ApiObject {
     $sql = $this->user->getSQL();
 
     $minutes = Session::DURATION;
-    $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in");
+    $columns = array("expires", "user_id", "ipAddress", "os", "browser", "data", "stay_logged_in", "csrf_token");
 
     $success = $sql
       ->insert("Session", $columns)
@@ -104,7 +107,8 @@ class Session extends ApiObject {
         $this->os,
         $this->browser,
         json_encode($_SESSION),
-        $stayLoggedIn)
+        $stayLoggedIn,
+        $this->csrfToken)
       ->returning("uid")
       ->execute();
 
@@ -135,8 +139,13 @@ class Session extends ApiObject {
       ->set("Session.os", $this->os)
       ->set("Session.browser", $this->browser)
       ->set("Session.data", json_encode($_SESSION))
+      ->set("Session.csrf_token", $this->csrfToken)
       ->where(new Compare("Session.uid", $this->sessionId))
       ->where(new Compare("Session.user_id", $this->user->getId()))
       ->execute();
   }
+
+  public function getCsrfToken(): string {
+    return $this->csrfToken;
+  }
 }

+ 16 - 8
core/Objects/User.class.php

@@ -71,12 +71,19 @@ class User extends ApiObject {
   }
 
   public function jsonSerialize() {
-    return array(
-      'uid' => $this->uid,
-      'name' => $this->username,
-      'language' => $this->language,
-      'session' => $this->session,
-    );
+    if ($this->isLoggedIn()) {
+      return array(
+        'uid' => $this->uid,
+        'name' => $this->username,
+        'groups' => $this->groups,
+        'language' => $this->language->jsonSerialize(),
+        'session' => $this->session->jsonSerialize(),
+      );
+    } else {
+      return array(
+         'language' => $this->language->jsonSerialize(),
+      );
+    }
   }
 
   private function reset() {
@@ -116,7 +123,7 @@ class User extends ApiObject {
   public function readData($userId, $sessionId, $sessionUpdate = true) {
 
     $res = $this->sql->select("User.name", "Language.uid as langId", "Language.code as langCode", "Language.name as langName",
-        "Session.data", "Session.stay_logged_in", "Group.uid as groupId", "Group.name as groupName")
+        "Session.data", "Session.stay_logged_in", "Session.csrf_token", "Group.uid as groupId", "Group.name as groupName")
         ->from("User")
         ->innerJoin("Session", "Session.user_id", "User.uid")
         ->leftJoin("Language", "User.language_id", "Language.uid")
@@ -134,9 +141,10 @@ class User extends ApiObject {
         $success = false;
       } else {
         $row = $res[0];
+        $csrfToken = $row["csrf_token"];
         $this->username = $row['name'];
         $this->uid = $userId;
-        $this->session = new Session($this, $sessionId);
+        $this->session = new Session($this, $sessionId, $csrfToken);
         $this->session->setData(json_decode($row["data"] ?? '{}'));
         $this->session->stayLoggedIn($row["stay_logged_in"]);
         if($sessionUpdate) $this->session->update();