Skip to content
Open
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
26 changes: 26 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,32 @@ abstract public function getSupportForAttributes(): bool;
*/
abstract public function getSupportForSchemaAttributes(): bool;

/**
* Can a permission be scoped to a single column?
*
* @return bool
*/
abstract public function getSupportForColumnPermissions(): bool;

/**
* Repoint column-scoped permissions at a renamed column.
*
* @param Document $collection
* @param string $old
* @param string $new
* @return array<string> ids of documents whose $permissions changed
*/
abstract public function renameColumnPermissions(Document $collection, string $old, string $new): array;

/**
* Drop every permission scoped to a column that no longer exists.
*
* @param Document $collection
* @param string $column
* @return array<string> ids of documents whose $permissions changed
*/
abstract public function deleteColumnPermissions(Document $collection, string $column): array;

/**
* Are schema indexes supported?
*
Expand Down
45 changes: 34 additions & 11 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,24 +189,36 @@ public function createCollection(string $name, array $attributes = [], array $in
$collection .= ")";
$collection = $this->trigger(Database::EVENT_COLLECTION_CREATE, $collection);

// _column scopes a permission to a single column. An empty string means
// every column, which is how every permission written before column-level
// permissions reads. It is NOT NULL on purpose: MySQL and MariaDB treat
// NULLs as distinct in a UNIQUE index, so a nullable _column would let
// duplicate permission rows slip past _index1.
//
// _index1 indexes it by prefix, not in full: these tables are utf8mb4 and the
// other four members already cost ~2098 of InnoDB's 3072-byte key limit, so a
// full VARCHAR(255) member would take it to ~3120 and the index would fail to
// build. MAX_UID_DEFAULT_LENGTH is the longest a column key may be, so the
// prefix is full uniqueness for every value that can actually be stored.
$permissions = "
CREATE TABLE {$this->getSQLTable($id . '_perms')} (
_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
_type VARCHAR(12) NOT NULL,
_permission VARCHAR(255) NOT NULL,
_column VARCHAR(255) NOT NULL DEFAULT '',
_document VARCHAR(255) NOT NULL,
PRIMARY KEY (_id),
";

if ($this->sharedTables) {
$permissions .= "
_tenant INT(11) UNSIGNED DEFAULT NULL,
UNIQUE INDEX _index1 (_document, _tenant, _type, _permission),
UNIQUE INDEX _index1 (_document, _tenant, _type, _permission, _column(" . Database::MAX_UID_DEFAULT_LENGTH . ")),
INDEX _permission (_tenant, _permission, _type)
";
} else {
$permissions .= "
UNIQUE INDEX _index1 (_document, _type, _permission),
UNIQUE INDEX _index1 (_document, _type, _permission, _column(" . Database::MAX_UID_DEFAULT_LENGTH . ")),
INDEX _permission (_permission, _type)
";
}
Expand Down Expand Up @@ -895,12 +907,14 @@ public function createDocument(Document $collection, Document $document): Docume
}

$permissions = [];
$permissionBinds = [];
foreach (Database::PERMISSIONS as $type) {
foreach ($document->getPermissionsByType($type) as $permission) {
foreach ($document->getPermissionsByTypeWithColumns($type) as $i => $permission) {
$tenantBind = $this->sharedTables ? ", :_tenant" : '';
$permission = \str_replace('"', '', $permission);
$permission = "('{$type}', '{$permission}', :_uid {$tenantBind})";
$permissions[] = $permission;
$role = \str_replace('"', '', $permission['role']);
$columnBind = ":_column_{$type}_{$i}";
$permissionBinds[$columnBind] = $permission['column'];
$permissions[] = "('{$type}', '{$role}', {$columnBind}, :_uid {$tenantBind})";
}
}

Expand All @@ -909,7 +923,7 @@ public function createDocument(Document $collection, Document $document): Docume
$permissions = \implode(', ', $permissions);

$sqlPermissions = "
INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn})
INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _column, _document {$tenantColumn})
VALUES {$permissions};
";

Expand All @@ -918,6 +932,9 @@ public function createDocument(Document $collection, Document $document): Docume
if ($this->sharedTables) {
$stmtPermissions->bindValue(':_tenant', $document->getTenant());
}
foreach ($permissionBinds as $key => $value) {
$stmtPermissions->bindValue($key, $value);
}
}

$stmt->execute();
Expand Down Expand Up @@ -1007,18 +1024,19 @@ public function updateDocument(Document $collection, string $id, Document $docum
$values = [];
$binds = [];
foreach (Database::PERMISSIONS as $type) {
foreach ($document->getPermissionsByType($type) as $i => $permission) {
foreach ($document->getPermissionsByTypeWithColumns($type) as $i => $permission) {
$tenantPlaceholder = $this->sharedTables ? ', :_tenant' : '';
$values[] = "( :_uid, '{$type}', :_add_{$type}_{$i} {$tenantPlaceholder})";
$binds[":_add_{$type}_{$i}"] = $permission;
$values[] = "( :_uid, '{$type}', :_add_{$type}_{$i}, :_addcol_{$type}_{$i} {$tenantPlaceholder})";
$binds[":_add_{$type}_{$i}"] = $permission['role'];
$binds[":_addcol_{$type}_{$i}"] = $permission['column'];
}
}

if (!empty($values)) {
$tenantColumn = $this->sharedTables ? ', _tenant' : '';

$sql = "
INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission {$tenantColumn})
INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission, _column {$tenantColumn})
VALUES " . \implode(', ', $values);

$sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql);
Expand Down Expand Up @@ -1771,6 +1789,11 @@ public function getSupportForUpsertOnUniqueIndex(): bool
return true;
}

public function getSupportForColumnPermissions(): bool
{
return true;
}

public function getSupportForSchemaAttributes(): bool
{
return true;
Expand Down
24 changes: 24 additions & 0 deletions src/Database/Adapter/Memory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,30 @@ public function getSchemaIndexes(string $collection): array
return [];
}

public function getSupportForColumnPermissions(): bool
{
return false;
}

/**
* Column-level permissions are not supported by this adapter, so a rename
* can never have column-scoped permissions to repoint.
*
* @param Document $collection
* @param string $old
* @param string $new
* @return array<string>
*/
public function renameColumnPermissions(Document $collection, string $old, string $new): array
{
return [];
}

public function deleteColumnPermissions(Document $collection, string $column): array
{
return [];
}

public function getTenantQuery(string $collection, string $alias = ''): string
{
return '';
Expand Down
25 changes: 25 additions & 0 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4202,13 +4202,38 @@ public function decodePolygon(string $wkb): array
return [];
}

public function getSupportForColumnPermissions(): bool
{
return false;
}

/**
* Column-level permissions are not supported by this adapter, so a rename
* can never have column-scoped permissions to repoint.
*
* @param Document $collection
* @param string $old
* @param string $new
* @return array<string>
*/
public function renameColumnPermissions(Document $collection, string $old, string $new): array
{
return [];
}

public function deleteColumnPermissions(Document $collection, string $column): array
{
return [];
}

/**
* Get the query to check for tenant when in shared tables mode
*
* @param string $collection The collection being queried
* @param string $alias The alias of the parent collection if in a subquery
* @return string
*/

public function getTenantQuery(string $collection, string $alias = ''): string
{
return '';
Expand Down
15 changes: 15 additions & 0 deletions src/Database/Adapter/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,21 @@ public function getSupportForAttributes(): bool
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForColumnPermissions(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function renameColumnPermissions(Document $collection, string $old, string $new): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function deleteColumnPermissions(Document $collection, string $column): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForSchemaAttributes(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
Expand Down
33 changes: 23 additions & 10 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ public function createCollection(string $name, array $attributes = [], array $in
_tenant INTEGER DEFAULT NULL,
_type VARCHAR(12) NOT NULL,
_permission VARCHAR(255) NOT NULL,
_column VARCHAR(255) NOT NULL DEFAULT '',
_document VARCHAR(255) NOT NULL
);
";
Expand All @@ -265,7 +266,7 @@ public function createCollection(string $name, array $attributes = [], array $in
$permissionIndex = $this->getShortKey("{$namespace}_{$this->tenant}_{$id}_permission");
$permissions .= "
CREATE UNIQUE INDEX \"{$uniquePermissionIndex}\"
ON {$this->getSQLTable($id . '_perms')} USING btree (_tenant,_document,_type,_permission);
ON {$this->getSQLTable($id . '_perms')} USING btree (_tenant,_document,_type,_permission,_column);
CREATE INDEX \"{$permissionIndex}\"
ON {$this->getSQLTable($id . '_perms')} USING btree (_tenant,_permission,_type);
";
Expand All @@ -274,7 +275,7 @@ public function createCollection(string $name, array $attributes = [], array $in
$permissionIndex = $this->getShortKey("{$namespace}_{$id}_permission");
$permissions .= "
CREATE UNIQUE INDEX \"{$uniquePermissionIndex}\"
ON {$this->getSQLTable($id . '_perms')} USING btree (_document COLLATE utf8_ci_ai,_type,_permission);
ON {$this->getSQLTable($id . '_perms')} USING btree (_document COLLATE utf8_ci_ai,_type,_permission,_column);
CREATE INDEX \"{$permissionIndex}\"
ON {$this->getSQLTable($id . '_perms')} USING btree (_permission,_type);
";
Expand Down Expand Up @@ -1046,11 +1047,14 @@ public function createDocument(Document $collection, Document $document): Docume
}

$permissions = [];
$permissionBinds = [];
foreach (Database::PERMISSIONS as $type) {
foreach ($document->getPermissionsByType($type) as $permission) {
$permission = \str_replace('"', '', $permission);
foreach ($document->getPermissionsByTypeWithColumns($type) as $i => $permission) {
$role = \str_replace('"', '', $permission['role']);
$sqlTenant = $this->sharedTables ? ', :_tenant' : '';
$permissions[] = "('{$type}', '{$permission}', :_uid {$sqlTenant})";
$columnBind = ":_column_{$type}_{$i}";
$permissionBinds[$columnBind] = $permission['column'];
$permissions[] = "('{$type}', '{$role}', {$columnBind}, :_uid {$sqlTenant})";
}
}

Expand All @@ -1060,7 +1064,7 @@ public function createDocument(Document $collection, Document $document): Docume
$sqlTenant = $this->sharedTables ? ', _tenant' : '';

$queryPermissions = "
INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$sqlTenant})
INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _column, _document {$sqlTenant})
VALUES {$permissions}
";

Expand All @@ -1070,6 +1074,9 @@ public function createDocument(Document $collection, Document $document): Docume
if ($sqlTenant) {
$stmtPermissions->bindValue(':_tenant', $document->getTenant());
}
foreach ($permissionBinds as $key => $value) {
$stmtPermissions->bindValue($key, $value);
}
}

try {
Expand Down Expand Up @@ -1133,18 +1140,19 @@ public function updateDocument(Document $collection, string $id, Document $docum
$values = [];
$binds = [];
foreach (Database::PERMISSIONS as $type) {
foreach ($document->getPermissionsByType($type) as $i => $permission) {
foreach ($document->getPermissionsByTypeWithColumns($type) as $i => $permission) {
$sqlTenant = $this->sharedTables ? ', :_tenant' : '';
$values[] = "( :_uid, '{$type}', :_add_{$type}_{$i} {$sqlTenant})";
$binds[":_add_{$type}_{$i}"] = $permission;
$values[] = "( :_uid, '{$type}', :_add_{$type}_{$i}, :_addcol_{$type}_{$i} {$sqlTenant})";
$binds[":_add_{$type}_{$i}"] = $permission['role'];
$binds[":_addcol_{$type}_{$i}"] = $permission['column'];
}
}

if (!empty($values)) {
$sqlTenant = $this->sharedTables ? ', _tenant' : '';

$sql = "
INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission {$sqlTenant})
INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission, _column {$sqlTenant})
VALUES " . \implode(', ', $values);

$sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql);
Expand Down Expand Up @@ -2090,6 +2098,11 @@ public function getSupportForIntegerBooleans(): bool
*
* @return bool
*/
public function getSupportForColumnPermissions(): bool
{
return true;
}

public function getSupportForSchemaAttributes(): bool
{
return false;
Expand Down
24 changes: 24 additions & 0 deletions src/Database/Adapter/Redis.php
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,30 @@ public function setSupportForAttributes(bool $support): bool
return true;
}

public function getSupportForColumnPermissions(): bool
{
return false;
}

/**
* Column-level permissions are not supported by this adapter, so a rename
* can never have column-scoped permissions to repoint.
*
* @param Document $collection
* @param string $old
* @param string $new
* @return array<string>
*/
public function renameColumnPermissions(Document $collection, string $old, string $new): array
{
return [];
}

public function deleteColumnPermissions(Document $collection, string $column): array
{
return [];
}

public function getSupportForSchemaAttributes(): bool
{
return false;
Expand Down
Loading
Loading