Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"description": "Collation to apply on SQL queries. Possible values are: 'utf8_unicode_ci' (3 bytes) or 'utf8mb4_unicode_ci' (4 bytes) in most cases.",
"instant": true,
"default": "utf8mb4_unicode_ci",
"example": "MARIADB"
"example": "utf8_unicode_ci"
},
"DB_HOST": {
"type": "string",
Expand All @@ -136,7 +136,7 @@
},
"DB_PORT": {
"type": "integer",
"description": "TCP/IP port for connecting to DB host.",
"description": "TCP/IP port for connecting to DB host. Defaut ports: MYSQL 3306, SQLSRV 1433",
"instant": true,
"environment": "EQ_DB_PORT",
"default": "3306"
Expand Down
84 changes: 48 additions & 36 deletions lib/equal/access/AccessController.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
namespace equal\access;

use equal\orm\ObjectManager;
use equal\orm\Model;
use equal\organic\Service;
use equal\services\Container;

Expand Down Expand Up @@ -34,28 +36,28 @@ public static function constants() {
}

protected function getUserGroups($user_id) {
$groups_ids = [];
if(!isset($this->groupsTable[$user_id])) {
$groups_ids = [];
if(!isset($this->groupsTable[$user_id])) {
// all users are members of default group (including unidentified users)
$this->groupsTable[$user_id] = [(string) QN_DEFAULT_GROUP_ID];

$orm = $this->container->get('orm');
$values = $orm->read('core\User', $user_id, ['groups_ids']);
if($values > 0 && isset($values[$user_id])) {
$this->groupsTable[$user_id] = array_unique(array_merge($this->groupsTable[$user_id], $values[$user_id]['groups_ids']));
if($values > 0 && isset($values[$user_id])) {
$this->groupsTable[$user_id] = array_unique(array_merge($this->groupsTable[$user_id], $values[$user_id]['groups_ids']));
}
}
$groups_ids = $this->groupsTable[$user_id];
return $groups_ids;
}

protected function getGroupUsers($group_id) {
$users_ids = [];
if(!isset($this->usersTable[$group_id])) {
$users_ids = [];
if(!isset($this->usersTable[$group_id])) {
$orm = $this->container->get('orm');
$values = $orm->read('core\Group', $group_id, ['users_ids']);
if($values > 0 && isset($values[$group_id])) {
$this->usersTable[$group_id] = $values[$group_id]['users_ids'];
if($values > 0 && isset($values[$group_id])) {
$this->usersTable[$group_id] = $values[$group_id]['users_ids'];
}
else {
$this->usersTable[$group_id] = [];
Expand All @@ -80,14 +82,14 @@ protected function getGroupUsers($group_id) {
* => performed during filter
*/
protected function getUserRights($user_id, $object_class, $object_fields=[], $object_ids=[]) {
// all users are at least granted the default permissions
$user_rights = $this->default_rights;
// all users are at least granted the default permissions
$user_rights = $this->default_rights;

// if we did already compute user rights then we're done!
if(isset($this->permissionsTable[$user_id][$object_class])) {
if(isset($this->permissionsTable[$user_id][$object_class])) {
$user_rights = $this->permissionsTable[$user_id][$object_class];
}
else {
else {
// root user always has full rights
if($user_id == QN_ROOT_USER_ID) {
return QN_R_CREATE | QN_R_READ | QN_R_WRITE | QN_R_DELETE | QN_R_MANAGE;
Expand All @@ -107,7 +109,7 @@ protected function getUserRights($user_id, $object_class, $object_fields=[], $ob
}
}
else {
$orm = $this->container->get('orm');
$orm = $this->container->get('orm');

$domains = [];
// add parent classes to the domain (when a right is granted on a class, it is also granted on children classes)
Expand Down Expand Up @@ -140,23 +142,23 @@ protected function getUserRights($user_id, $object_class, $object_fields=[], $ob
if(count($acl_ids)) {
// get the user permissions
$values = $orm->read('core\Permission', $acl_ids, ['rights']);
foreach($values as $acl_id => $row) {
$user_rights |= $row['rights'];
foreach($values as $aid => $acl) {
$user_rights |= $acl['rights'];
}
}

if(!isset($this->permissionsTable[$user_id])) $this->permissionsTable[$user_id] = array();
// #todo - first element ([0]) of the class-related array could be used to store the user permissions for the whole class
$this->permissionsTable[$user_id][$object_class] = $user_rights;
}
}
}

return $user_rights;
}
return $user_rights;
}


protected function getUserAcls($user_id, $object_class) {
$acls = [];
$acls = [];

// get user groups
$groups_ids = $this->getUserGroups($user_id);
Expand Down Expand Up @@ -202,8 +204,8 @@ protected function getUserAcls($user_id, $object_class) {
$acls = $orm->read('core\Permission', $acl_ids, ['rights']);
}

return $acls;
}
return $acls;
}


/**
Expand Down Expand Up @@ -343,27 +345,31 @@ public function hasGroup($group) {
*
* @param integer $operation Identifier of the operation(s) that is/are checked (binary mask made of constants : QN_R_CREATE, QN_R_READ, QN_R_DELETE, QN_R_WRITE, QN_R_MANAGE).
* @param string $object_class Class selector indicating on which classes the check must be performed.
* @param array<string> $object_fields (optional) List of fields name on which the operation must be granted.
* @param array<int> $object_ids (optional) List of objects identifiers (relating to $object_class) against which the check must be performed.
* @param string[] $object_fields (optional) List of fields name on which the operation must be granted.
* @param int[] $object_ids (optional) List of objects identifiers (relating to $object_class) against which the check must be performed.
*/
public function isAllowed($operation, $object_class='*', $object_fields=[], $object_ids=[]) {

// grant all rights when using CLI
if(php_sapi_name() === 'cli') return true;
if(php_sapi_name() === 'cli') {
return true;
}

// check operation against default rights
if($this->default_rights & $operation) return true;
if($this->default_rights & $operation) {
return true;
}

$allowed = false;

// retrieve current user identifier
$auth = $this->container->get('auth');
$user_id = $auth->userId();

// force cast ids to array (passing a single id is accepted)
if(!is_array($object_ids)) {
$object_ids = (array) $object_ids;
}
// force cast ids to array (passing a single id is accepted)
if(!is_array($object_ids)) {
$object_ids = (array) $object_ids;
}

// permission query is for class and/or fields only (no specific objects)
if(!count($object_ids)) {
Expand Down Expand Up @@ -413,6 +419,7 @@ public function filter($operation, $object_class, $object_fields, $object_ids) {
// grant all rights when using CLI
if(php_sapi_name() === 'cli') return $object_ids;

/** @var ObjectManager */
$orm = $this->container->get('orm');

// retrieve current user identifier
Expand All @@ -422,15 +429,20 @@ public function filter($operation, $object_class, $object_fields, $object_ids) {
// build final user rights
$user_rights = $this->getUserRights($user_id, $object_class, $object_fields, $object_ids);

// user always has READ rights on its own object
if($orm::getObjectRootClass($object_class) == 'core\User' && count($object_ids) == 1 && $user_id == $object_ids[0]) {
// user has some rights on its own object
if(ObjectManager::getObjectRootClass($object_class) == 'core\User' && count($object_ids) == 1 && $user_id == $object_ids[0]) {
// user always has READ rights on its own object
$user_rights |= QN_R_READ;
// user can update some fields on its own object
$writeable_fields = ['password', 'firstname', 'lastname', 'language', 'locale'];
// if, after removing special fields, there are only fields that user can update, then we grant the WRITE right
if(count(array_diff($object_fields, array_merge(array_keys(Model::getSpecialColumns()), $writeable_fields))) == 0) {
$user_rights |= QN_R_WRITE;
}
}
else if($operation == QN_R_READ) {
else if($operation == QN_R_READ) {
// this is a special case of a generic feature (we should add this in the init data)

// if all fields 'creator' of targeted objects are equal to $user_id, then add R_READ to user_rights

$objects = $orm->read($object_class, $object_ids, ['creator']);
$user_ids = [];
foreach($objects as $oid => $odata) {
Expand All @@ -440,7 +452,7 @@ public function filter($operation, $object_class, $object_fields, $object_ids) {
if(count($user_ids) == 1 && array_keys($user_ids)[0] == $user_id) {
$user_rights |= QN_R_READ;
}
}
}


/*
Expand All @@ -454,6 +466,6 @@ public function filter($operation, $object_class, $object_fields, $object_ids) {
validate id as soon as an ACL condition is met
*/

return ((bool)($user_rights & $operation))?$object_ids:[];
return ((bool)($user_rights & $operation))?$object_ids:[];
}
}
3 changes: 2 additions & 1 deletion lib/equal/db/DBManipulatorMySQL.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public function getQueryAddColumn($table_name, $column_name, $def) {
if(isset($def['null']) && !$def['null']) {
$sql .= ' NOT NULL';
}
// set query according to primary key property
if(isset($def['auto_increment']) && $def['auto_increment']) {
$sql .= ' AUTO_INCREMENT';
}
Expand All @@ -155,7 +156,7 @@ public function getQueryAddColumn($table_name, $column_name, $def) {
$sql .= ';';

if(isset($def['primary']) && $def['primary']) {
"ALTER TABLE `{$table_name}` ADD PRIMARY KEY (`{$column_name}`);";
$sql .= "ALTER TABLE `{$table_name}` ADD PRIMARY KEY (`{$column_name}`);";
}
return $sql;
}
Expand Down
18 changes: 6 additions & 12 deletions lib/equal/db/DBManipulatorSqlSrv.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ public function getTableConstraints($table_name) {
}

public function getQueryCreateTable($table_name) {
// #memo - we must add at least one column, so as a convention we add the id column
return "IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '{$table_name}'))\n
// #memo - we must add at least one column for the query to be valid, so as a convention we add the id column
return "IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG = '{$this->db_name}' AND TABLE_NAME = '{$table_name}'))\n
BEGIN\n
CREATE TABLE [{$table_name}] ([id] int IDENTITY(1, 1) NOT NULL)\n
END;";
Expand All @@ -167,14 +167,13 @@ public function getQueryAddColumn($table_name, $column_name, $def) {
if(isset($def['null']) && !$def['null']) {
$sql .= ' NOT NULL';
}

// set query according to primary key property
if(isset($def['primary']) && $def['primary']) {
$sql .= ' IDENTITY';
if(isset($def['auto_increment']) && $def['auto_increment']) {
$sql .= '(1,1)';
}
}

// #memo - default is supported by ORM, not DBMS
if(!isset($def['null']) || $def['null']) {
// unless specified otherwise, all columns can be null (even if having a default value)
Expand Down Expand Up @@ -234,16 +233,11 @@ function sendQuery($query, $sql_operation='') {
}

if(($result = sqlsrv_query($this->dbms_handler, $query)) === false) {
$error_str = '';
if( ($errors = sqlsrv_errors() ) != null) {
$error_str = implode(',',reset($errors));
/*
foreach( $errors as $error ) {
$error_str .= implode(',', $error);
}
*/
$errors = reset($errors);
$errors = ((isset($errors['code']))?'('.$errors['code'].') ':'').((isset($errors['message']))?$errors['message']:'');
}
throw new \Exception(__METHOD__.' : query failure. '.$error_str.'. For query: "'.$query.'"', QN_ERROR_SQL);
throw new \Exception(__METHOD__.' : query failure. '.$errors.'. For query: "'.$query.'"', QN_ERROR_SQL);
}
// everything went well: perform additional operations (replication & info about query result)
else {
Expand Down
44 changes: 29 additions & 15 deletions lib/equal/orm/ObjectManager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,20 @@ class ObjectManager extends Service {

public static $valid_attributes = [
'alias' => array('description', 'help', 'type', 'visible', 'default', 'usage', 'alias', 'required'),
'boolean' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate'),
'integer' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'selection', 'unique'),
'float' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'selection', 'precision'),
'string' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'multilang', 'selection', 'unique'),
'text' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'date' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate'),
'time' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'required', 'onupdate'),
'datetime' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate'),
'file' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'binary' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'many2one' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'required', 'foreign_object', 'domain', 'onupdate', 'ondelete', 'multilang'),
'one2many' => array('description', 'help', 'type', 'visible', 'default', 'foreign_object', 'foreign_field', 'domain', 'onupdate', 'ondetach', 'order', 'sort'),
'many2many' => array('description', 'help', 'type', 'visible', 'default', 'foreign_object', 'foreign_field', 'rel_table', 'rel_local_key', 'rel_foreign_key', 'domain', 'onupdate'),
'computed' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'result_type', 'usage', 'function', 'onupdate', 'store', 'multilang', 'selection', 'foreign_object')
'boolean' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate'),
'integer' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'selection', 'unique'),
'float' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'selection', 'precision'),
'string' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'multilang', 'selection', 'unique'),
'text' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'date' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate'),
'time' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'required', 'onupdate'),
'datetime' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate'),
'file' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'binary' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'usage', 'required', 'onupdate', 'multilang'),
'many2one' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'readonly', 'required', 'foreign_object', 'domain', 'onupdate', 'ondelete', 'multilang'),
'one2many' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'foreign_object', 'foreign_field', 'domain', 'onupdate', 'ondetach', 'order', 'sort'),
'many2many' => array('description', 'help', 'type', 'visible', 'default', 'dependencies', 'foreign_object', 'foreign_field', 'rel_table', 'rel_local_key', 'rel_foreign_key', 'domain', 'onupdate'),
'computed' => array('description', 'help', 'type', 'visible', 'default', 'readonly', 'result_type', 'usage', 'function', 'onupdate', 'store', 'instant', 'multilang', 'selection', 'foreign_object')
];

public static $mandatory_attributes = [
Expand Down Expand Up @@ -1549,10 +1549,24 @@ public function update($class, $ids=null, $fields=null, $lang=null, $create=fals


// 8) handle the resetting of the dependent computed fields
// #todo $dependencies

if(count($dependencies)) {
// remember fields that must be re-computed instantly
$instant_fields = [];
foreach($dependencies as $dependency) {
if(isset($schema[$dependency]) && $schema[$dependency]['type'] == 'computed') {
if(isset($schema[$dependency]['instant']) && $schema[$dependency]['instant']) {
$instant_fields[] = $dependency;
}
foreach($ids as $oid) {
$this->cache[$table_name][$oid][$lang][$dependency] = null;
}
}
}
$this->store($class, $ids, $dependencies, $lang);
if(count($instant_fields)) {
// re-compute 'instant' computed field
$this->read($class, $ids, $instant_fields, $lang);
}
}
}
Expand Down
Loading