From 24cf74623987e7e3df56c237ca1732faacb00b0a Mon Sep 17 00:00:00 2001
From: Damien <damien.burel@maarch.org>
Date: Wed, 28 Nov 2018 18:05:36 +0100
Subject: [PATCH] FEAT #8886 User groups and privileges

---
 sql/data_fr.sql                               | 15 +++++
 sql/structure.sql                             | 31 +++++++++++
 .../controllers/DocumentController.php        | 16 ++++--
 src/app/user/controllers/UserController.php   | 41 +++++++++++++-
 src/app/user/models/UserGroupModel.php        | 55 +++++++++++++++++++
 5 files changed, 150 insertions(+), 8 deletions(-)
 create mode 100755 src/app/user/models/UserGroupModel.php

diff --git a/sql/data_fr.sql b/sql/data_fr.sql
index 788222f20b..0bb231951f 100755
--- a/sql/data_fr.sql
+++ b/sql/data_fr.sql
@@ -6,6 +6,21 @@ ALTER SEQUENCE users_id_seq RESTART WITH 1;
 INSERT INTO users (email, password, firstname, lastname, mode) VALUES ('jjane@maarch.com', '$2y$10$C.QSslBKD3yNMfRPuZfcaubFwPKiCkqqOUyAdOr5FSGKPaePwuEjG', 'Jenny', 'JANE', 'standard');
 INSERT INTO users (email, password, firstname, lastname, mode) VALUES ('ccornillac@maarch.com', '$2y$10$C.QSslBKD3yNMfRPuZfcaubFwPKiCkqqOUyAdOr5FSGKPaePwuEjG', 'Clovis', 'CORNILLAC', 'rest');
 
+------------
+--GROUPS
+------------
+TRUNCATE TABLE groups;
+ALTER SEQUENCE groups_id_seq RESTART WITH 2;
+INSERT INTO groups (id, label) VALUES (1, 'Administrateur');
+
+TRUNCATE TABLE groups_privileges;
+ALTER SEQUENCE groups_privileges_id_seq RESTART WITH 1;
+INSERT INTO groups_privileges (group_id, privilege) VALUES (1, 'manage_rest_users');
+
+TRUNCATE TABLE users_groups;
+ALTER SEQUENCE users_groups_id_seq RESTART WITH 1;
+INSERT INTO users_groups (group_id, user_id) VALUES (1, 1);
+
 ------------
 --DOCSERVERS
 ------------
diff --git a/sql/structure.sql b/sql/structure.sql
index 6987b5b29e..6679c16f16 100755
--- a/sql/structure.sql
+++ b/sql/structure.sql
@@ -89,6 +89,37 @@ CREATE TABLE users
 )
 WITH (OIDS=FALSE);
 
+DROP TABLE IF EXISTS groups;
+CREATE TABLE groups
+(
+  id serial NOT NULL,
+  label character varying(128) NOT NULL,
+  CONSTRAINT groups_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS groups_privileges;
+CREATE TABLE groups_privileges
+(
+  id serial NOT NULL,
+  group_id INTEGER NOT NULL,
+  privilege character varying(128) NOT NULL,
+  CONSTRAINT groups_privileges_pkey PRIMARY KEY (id),
+  CONSTRAINT groups_privileges_unique_key UNIQUE (group_id, privilege)
+)
+WITH (OIDS=FALSE);
+
+DROP TABLE IF EXISTS users_groups;
+CREATE TABLE users_groups
+(
+    id serial NOT NULL,
+    group_id INTEGER NOT NULL,
+    user_id INTEGER NOT NULL,
+    CONSTRAINT users_groups_pkey PRIMARY KEY (id),
+    CONSTRAINT users_groups_unique_key UNIQUE (group_id, user_id)
+)
+WITH (OIDS=FALSE);
+
 DROP TABLE IF EXISTS docservers;
 CREATE TABLE docservers
 (
diff --git a/src/app/document/controllers/DocumentController.php b/src/app/document/controllers/DocumentController.php
index a17ad735d8..92fb1f80f0 100755
--- a/src/app/document/controllers/DocumentController.php
+++ b/src/app/document/controllers/DocumentController.php
@@ -318,10 +318,13 @@ class DocumentController
 
     public function getHandwrittenDocumentById(Request $request, Response $response, array $args)
     {
-        if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
-            return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
+        $user = UserModel::getById(['select' => ['mode'], 'id' => $GLOBALS['id']]);
+        if ($user['mode'] != 'rest') {
+            if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
+                return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
+            }
         }
-
+        
         $adr = AdrModel::getDocumentsAdr([
             'select'    => ['path', 'filename', 'fingerprint'],
             'where'     => ['main_document_id = ?', 'type = ?'],
@@ -351,8 +354,11 @@ class DocumentController
 
     public function getStatusById(Request $request, Response $response, array $args)
     {
-        if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
-            return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
+        $user = UserModel::getById(['select' => ['mode'], 'id' => $GLOBALS['id']]);
+        if ($user['mode'] != 'rest') {
+            if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
+                return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
+            }
         }
 
         $document = DocumentModel::getById(['select' => ['status', 'mode'], 'id' => $args['id']]);
diff --git a/src/app/user/controllers/UserController.php b/src/app/user/controllers/UserController.php
index 9484873384..560e5370ad 100755
--- a/src/app/user/controllers/UserController.php
+++ b/src/app/user/controllers/UserController.php
@@ -22,12 +22,19 @@ use Slim\Http\Request;
 use Slim\Http\Response;
 use SrcCore\controllers\PasswordController;
 use SrcCore\models\AuthenticationModel;
+use SrcCore\models\ValidatorModel;
+use User\models\UserGroupModel;
 use User\models\UserModel;
 
 class UserController
 {
     public function get(Request $request, Response $response)
     {
+        $user = UserModel::getById(['select' => ['mode'], 'id' => $GLOBALS['id']]);
+        if ($user['mode'] != 'rest') {
+            return $response->withStatus(403)->withJson(['errors' => 'Route forbidden']);
+        }
+
         $users = UserModel::get([
             'select'    => ['id', 'firstname', 'lastname'],
             'where'     => ['mode = ?'],
@@ -50,11 +57,18 @@ class UserController
             $user['picture'] = 'data:image/png;base64,' . $user['picture'];
         }
 
+        $user['canManageRestUsers'] = UserController::hasPrivilege(['userId' => $args['id'], 'privilege' => 'manage_rest_users']);
+
         return $response->withJson(['user' => $user]);
     }
 
     public function create(Request $request, Response $response)
     {
+        $user = UserModel::getById(['select' => ['mode'], 'id' => $GLOBALS['id']]);
+        if ($user['mode'] != 'rest') {
+            return $response->withStatus(403)->withJson(['errors' => 'Route forbidden']);
+        }
+
         $data = $request->getParams();
 
         if (!Validator::stringType()->notEmpty()->validate($data['firstname'])) {
@@ -69,7 +83,7 @@ class UserController
 
         $existingUser = UserModel::getByEmail(['email' => $data['email'], 'select' => ['id']]);
         if (!empty($existingUser)) {
-            return $response->withStatus(400)->withJson(['errors' => 'User already exist']);
+            return $response->withStatus(400)->withJson(['errors' => 'User already exists']);
         }
 
         $logingModes = ['standard', 'rest'];
@@ -146,8 +160,12 @@ class UserController
 
     public function updatePassword(Request $request, Response $response, array $args)
     {
+        $user = UserModel::getById(['select' => ['email', 'mode'], 'id' => $args['id']]);
+
         if ($GLOBALS['id'] != $args['id']) {
-            return $response->withStatus(403)->withJson(['errors' => 'User out of perimeter']);
+            if ($user['mode'] != 'rest' || !UserController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_rest_users'])) {
+                return $response->withStatus(403)->withJson(['errors' => 'User out of perimeter']);
+            }
         }
 
         $data = $request->getParams();
@@ -158,7 +176,6 @@ class UserController
             return $response->withStatus(400)->withJson(['errors' => 'Bad Request']);
         }
 
-        $user = UserModel::getById(['select' => ['email'], 'id' => $args['id']]);
         if ($data['newPassword'] != $data['passwordConfirmation']) {
             return $response->withStatus(400)->withJson(['errors' => 'New password does not match password confirmation']);
         } elseif (!AuthenticationModel::authentication(['email' => $user['email'], 'password' => $data['currentPassword']])) {
@@ -284,4 +301,22 @@ class UserController
 
         return $response->withJson(['success' => 'success']);
     }
+
+    public function hasPrivilege(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['userId', 'privilege']);
+        ValidatorModel::intVal($args, ['userId']);
+        ValidatorModel::stringType($args, ['privilege']);
+
+        $groups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['userId']]]);
+
+        foreach ($groups as $group) {
+            $privilege = UserGroupModel::getPrivileges(['select' => [1], 'where' => ['group_id = ?', 'privilege = ?'], 'data' => [$group['group_id'], $args['privilege']]]);
+            if (!empty($privilege)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/src/app/user/models/UserGroupModel.php b/src/app/user/models/UserGroupModel.php
new file mode 100755
index 0000000000..8750ccc956
--- /dev/null
+++ b/src/app/user/models/UserGroupModel.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under licence GPLv3.
+* See LICENCE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+*
+*/
+
+/**
+* @brief User Group Model
+* @author dev@maarch.org
+*/
+
+namespace User\models;
+
+use SrcCore\models\DatabaseModel;
+use SrcCore\models\ValidatorModel;
+
+class UserGroupModel
+{
+    public static function get(array $aArgs)
+    {
+        ValidatorModel::arrayType($aArgs, ['select', 'where', 'data', 'orderBy']);
+        ValidatorModel::intType($aArgs, ['limit']);
+
+        $usersGroups = DatabaseModel::select([
+            'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
+            'table'     => ['users_groups'],
+            'where'     => empty($aArgs['where']) ? [] : $aArgs['where'],
+            'data'      => empty($aArgs['data']) ? [] : $aArgs['data'],
+            'orderBy'   => empty($aArgs['orderBy']) ? [] : $aArgs['orderBy'],
+            'limit'     => empty($aArgs['limit']) ? 0 : $aArgs['limit']
+        ]);
+
+        return $usersGroups;
+    }
+
+    public static function getPrivileges(array $aArgs)
+    {
+        ValidatorModel::arrayType($aArgs, ['select', 'where', 'data', 'orderBy']);
+        ValidatorModel::intType($aArgs, ['limit']);
+
+        $groupsPrivileges = DatabaseModel::select([
+            'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
+            'table'     => ['groups_privileges'],
+            'where'     => empty($aArgs['where']) ? [] : $aArgs['where'],
+            'data'      => empty($aArgs['data']) ? [] : $aArgs['data'],
+            'orderBy'   => empty($aArgs['orderBy']) ? [] : $aArgs['orderBy'],
+            'limit'     => empty($aArgs['limit']) ? 0 : $aArgs['limit']
+        ]);
+
+        return $groupsPrivileges;
+    }
+}
-- 
GitLab