Find this useful? Enter your email to receive occasional updates for securing PHP code.
Signing you up...
Thank you for signing up!
PHP Decode
<?php declare(strict_types=1); /** * Passbolt ~ Open source password manager for teams ..
Decoded Output download
<?php
declare(strict_types=1);
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 2.0.0
*/
namespace App\Model\Table;
use App\Model\Entity\Permission;
use App\Model\Entity\Resource;
use App\Model\Entity\Role;
use App\Model\Rule\IsNotSoftDeletedRule;
use App\Model\Traits\Resources\ResourcesFindersTrait;
use App\Model\Validation\DateTime\IsParsableDateTimeValidationRule;
use App\Utility\Application\FeaturePluginAwareTrait;
use App\Utility\UuidFactory;
use Cake\Event\Event;
use Cake\I18n\FrozenTime;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Validation\Validation;
use Cake\Validation\Validator;
use Passbolt\ResourceTypes\Model\Table\ResourceTypesTable;
/**
* Resources Model
*
* @property \App\Model\Table\UsersTable&\Cake\ORM\Association\HasOne $Creator
* @property \App\Model\Table\UsersTable&\Cake\ORM\Association\HasOne $Modifier
* @property \App\Model\Table\SecretsTable&\Cake\ORM\Association\HasMany $Secrets
* @property \App\Model\Table\PermissionsTable&\Cake\ORM\Association\HasMany $Permissions
* @method \App\Model\Entity\Resource get($primaryKey, $options = [])
* @method \App\Model\Entity\Resource[] newEntities(array $data, array $options = [])
* @method \App\Model\Entity\Resource|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \App\Model\Entity\Resource patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* @method \App\Model\Entity\Resource[] patchEntities(iterable $entities, array $data, array $options = [])
* @method \App\Model\Entity\Resource findOrCreate($search, ?callable $callback = null, $options = [])
* @mixin \Cake\ORM\Behavior\TimestampBehavior
* @property \App\Model\Table\FavoritesTable&\Cake\ORM\Association\HasOne $Favorites
* @property \App\Model\Table\PermissionsTable&\Cake\ORM\Association\HasOne $Permission
* @property \Passbolt\ResourceTypes\Model\Table\ResourceTypesTable&\Cake\ORM\Association\BelongsTo $ResourceTypes
* @property \Passbolt\Log\Model\Table\EntitiesHistoryTable&\Cake\ORM\Association\BelongsTo $EntitiesHistory
* @method \App\Model\Entity\Resource newEmptyEntity()
* @method \App\Model\Entity\Resource newEntity(array $data, array $options = [])
* @method \App\Model\Entity\Resource saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface>|false saveMany(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface> saveManyOrFail(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface>|false deleteMany(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface> deleteManyOrFail(iterable $entities, $options = [])
* @method \Cake\ORM\Query findByIdAndDeleted(string $id, bool $delete)
*/
class ResourcesTable extends Table
{
use ResourcesFindersTrait;
use FeaturePluginAwareTrait;
public const DESCRIPTION_MAX_LENGTH = 10000;
public const NAME_MAX_LENGTH = 255;
public const URI_MAX_LENGTH = 1024;
public const USERNAME_MAX_LENGTH = 255;
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('resources');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasOne('Creator', [
'className' => 'Users',
'bindingKey' => 'created_by',
'foreignKey' => 'id',
]);
$this->hasOne('Favorites', [
'foreignKey' => 'foreign_key',
]);
$this->hasOne('Modifier', [
'className' => 'Users',
'bindingKey' => 'modified_by',
'foreignKey' => 'id',
]);
$this->hasOne('Permission', [
'className' => 'Permissions',
'foreignKey' => 'aco_foreign_key',
'joinType' => 'INNER',
]);
$this->hasMany('Permissions', [
'foreignKey' => 'aco_foreign_key',
'saveStrategy' => 'replace',
// Important so that we can track the delete event and log it.
'cascadeCallbacks' => true,
]);
$this->hasMany('Secrets', [
'foreignKey' => 'resource_id',
'saveStrategy' => 'replace',
]);
$this->belongsTo('ResourceTypes');
}
/**
* Default validation rules.
*
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->uuid('id', __('The identifier should be a valid UUID.'))
->allowEmptyString('id', __('The identifier should not be empty.'), 'create');
$validator
->utf8Extended('name', __('The name should be a valid UTF8 string.'))
->maxLength(
'name',
self::NAME_MAX_LENGTH,
__('The name length should be maximum {0} characters.', self::NAME_MAX_LENGTH)
)
->requirePresence('name', 'create', __('A name is required.'))
->allowEmptyString('name', __('The name should not be empty.'), false);
$validator
->utf8Extended('username', __('The username should be a valid UTF8 string.'))
->maxLength(
'username',
self::USERNAME_MAX_LENGTH,
__('The username length should be maximum {0} characters.', self::USERNAME_MAX_LENGTH)
)
->allowEmptyString('username');
$validator
->utf8('uri', __('The uri should be a valid BMP-UTF8 string.'))
->maxLength(
'uri',
self::URI_MAX_LENGTH,
__('The uri length should be maximum {0} characters.', self::URI_MAX_LENGTH)
)
->allowEmptyString('uri');
$validator
->utf8Extended('description', __('The description should be a valid UTF8 string.'))
->maxLength(
'description',
self::DESCRIPTION_MAX_LENGTH,
__('The description length should be maximum {0} characters.', self::DESCRIPTION_MAX_LENGTH)
)
->allowEmptyString('description');
$validator
->boolean('deleted', __('The deleted status should be a valid boolean.'))
->allowEmptyString('deleted', __('The deleted status should not be empty'), false);
$validator
->allowEmptyDateTime('expired')
->add('expired', 'expired', new IsParsableDateTimeValidationRule());
$validator
->uuid('created_by', __('The identifier of the user who created the resource should be a valid UUID.'))
->requirePresence(
'created_by',
'create',
__('The identifier of the user who created the resource is required.')
)
->allowEmptyString(
'created_by',
__('The identifier of the user who created the resource should not be empty.'),
false
);
$validator
->uuid(
'modified_by',
__('The identifier of the user who last modified the resource should be a valid UUID.')
)
->requirePresence(
'modified_by',
'create',
__('The identifier of the user who last modified the resource required.')
)
->allowEmptyString(
'modified_by',
__('The identifier of the user who last modified the resource should not be empty.'),
false
);
$validator
->uuid('resource_type_id', __('The resource type identifier should be a valid UUID.'))
->requirePresence('resource_type_id', 'create', __('A resource type identifier is required.'));
// Associated fields
$validator
->requirePresence('permissions', 'create', __('The permissions are required.'))
// @todo Secrets is an array
->allowEmptyString('permissions', __('The permissions should not be empty.'), false)
->hasAtMost(
'permissions',
1,
__('The permissions should contain only the permission of the owner.'),
'create'
);
$validator
->requirePresence('secrets', 'create', __('The owner secret is required.'))
// @todo Secrets is an array
->allowEmptyString('secrets', __('The secrets should not be empty.'), false)
->hasAtMost('secrets', 1, __('The secrets should contain only the secret of the owner.'), 'create');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
// Create and Update rules
$rules->add($rules->existsIn('resource_type_id', 'ResourceTypes'), 'resource_type_exists', [
'message' => __('The resource type does not exist.'),
]);
// Create rules.
$rules->addCreate([$this, 'isOwnerPermissionProvidedRule'], 'owner_permission_provided', [
'errorField' => 'permissions',
'message' => __('The permissions should contain the owner permission.'),
]);
$rules->addCreate([$this, 'isOwnerSecretProvidedRule'], 'owner_secret_provided', [
'errorField' => 'secrets',
'message' => __('The secrets should contain the owner secret.'),
]);
// Update rules.
$rules->addUpdate([$this, 'isSecretsProvidedRule'], 'secrets_provided', [
'errorField' => 'secrets',
'message' => __('The secrets should contain the secrets of all the users having access to the resource.'),
]);
$rules->addUpdate(new IsNotSoftDeletedRule(), 'resource_is_not_soft_deleted', [
'table' => 'Resources',
'errorField' => 'id',
'message' => __('The resource does not exist.'),
]);
$rules->addUpdate([$this, 'isOwnerPermissionProvidedRule'], 'at_least_one_owner', [
'errorField' => 'permissions',
'message' => __('The permissions should contain at least the owner permission.'),
]);
return $rules;
}
/**
* @param \Cake\Event\Event $event event
* @param \ArrayObject $data data
* @param \ArrayObject $options options
* @return void
* @deprecated can be removed once isDescriptionEmptyOnPasswordAndDescriptionResourceType is a rule
*/
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options): void
{
if (!$this->isDescriptionEmptyOnPasswordAndDescriptionResourceType($data)) {
$data['description'] = null;
}
if (isset($data['expired']) && !empty($data['expired'])) {
// Parse the expired date into a time object
try {
$data['expired'] = FrozenTime::parse($data['expired']);
} catch (\Throwable $e) {
// If the expired date cannot be parsed, let the validation
// handle the fail
}
}
}
/**
* Validate that the entity has at least one owner
*
* @param \App\Model\Entity\Resource $entity The entity that will be created or updated.
* @param array|null $options options
* @return bool
*/
public function isOwnerPermissionProvidedRule(Resource $entity, ?array $options = [])
{
if (isset($entity->permissions)) {
$found = Hash::extract($entity->permissions, '{n}[type=' . Permission::OWNER . ']');
if (empty($found)) {
return false;
}
}
return true;
}
/**
* Validate that the entity description is empty if the resource type is password and description
* This can be refactored as a rule after 4.0.0
*
* @param \ArrayObject $data data
* @return bool
* @todo refactor this as a rule after 4.0.0
*/
public function isDescriptionEmptyOnPasswordAndDescriptionResourceType(\ArrayObject $data): bool
{
$resourceTypeId = $data['resource_type_id'] ?? null;
$description = $data['description'] ?? null;
$resourceTypePasswordAndDescriptionId = UuidFactory::uuid('resource-types.id.password-and-description');
$isResourceTypePasswordAndDescription = $resourceTypeId === $resourceTypePasswordAndDescriptionId;
if ($isResourceTypePasswordAndDescription && !empty($description)) {
return false;
}
return true;
}
/**
* Validate that a resource can be created only if the secret of the owner is provided.
*
* @param \App\Model\Entity\Resource $entity The entity that will be created.
* @param array|null $options options
* @return bool
*/
public function isOwnerSecretProvidedRule(Resource $entity, ?array $options = [])
{
return $entity->secrets[0]->user_id === $entity->created_by;
}
/**
* Validate that the secrets of all the allowed users are provided if the secret changed.
*
* @param \App\Model\Entity\Resource $entity The entity that will be created.
* @param array|null $options options
* @return bool
*/
public function isSecretsProvidedRule(Resource $entity, ?array $options = [])
{
// Secrets are not required to update a resource, but if provided check that the list of secrets correspond
// only to the users who have access to the resource.
if (!isset($entity->secrets)) {
return true;
}
// Retrieve the users who are allowed to access the resource.
/** @var \App\Model\Table\UsersTable $Users */
$Users = TableRegistry::getTableLocator()->get('Users');
$usersFindOptions['filter']['has-access'] = [$entity->id];
$allowedUsersIds = $Users->findIndex(Role::USER, $usersFindOptions)
->all()
->extract('id')
->toArray();
// Extract the users for whom the secrets will be updated.
$secretsUsersIds = Hash::extract($entity->secrets, '{n}.user_id');
// If the list of secrets does not correspond to the list of users who have access to the resource,
// do not validate.
if (
count($secretsUsersIds) != count($allowedUsersIds)
|| !empty(array_diff($allowedUsersIds, $secretsUsersIds))
) {
return false;
}
return true;
}
/**
* Soft delete a resource.
*
* @param string $userId The user who perform the delete.
* @param \App\Model\Entity\Resource $resource The resource to delete.
* @throws \InvalidArgumentException if the user id is not a uuid
* @return bool true if success
*/
public function softDelete(string $userId, Resource $resource): bool
{
// The softDelete will perform an update to the entity to soft delete it.
if (!Validation::uuid($userId)) {
throw new \InvalidArgumentException('The user identifier should be a valid UUID.');
}
if ($resource->deleted) {
$resource->setError('deleted', [
'is_not_soft_deleted' => __('The resource should not be already soft deleted.'),
]);
return false;
}
$acoType = PermissionsTable::RESOURCE_ACO;
if (!$this->Permissions->hasAccess($acoType, $resource->id, $userId, Permission::UPDATE)) {
$resource->setError('id', [
'has_access' => __('The user cannot delete this resource.'),
]);
return false;
}
// Patch the entity.
$data = [
'deleted' => true,
'modified_by' => $userId,
'secrets' => [],
// cleanup sensitive data
'username' => null,
'uri' => null,
'description' => null,
];
$patchOptions = [
'accessibleFields' => [
'username' => true,
'uri' => true,
'description' => true,
'deleted' => true,
'secrets' => true,
'modified' => true,
'modified_by' => true,
],
];
$this->patchEntity($resource, $data, $patchOptions);
if ($resource->getErrors()) {
return false;
}
// Soft delete the resource.
$this->save($resource, ['checkRules' => false]);
if ($resource->getErrors()) {
return false;
}
// Remove all the associated permissions.
$this->getAssociation('Permissions')
->deleteAll(['Permissions.aco_foreign_key' => $resource->id]);
// Remove all the associated favorites.
$this->getAssociation('Favorites')
->deleteAll(['Favorites.foreign_key' => $resource->id]);
// Notify other components about the resource soft delete.
$event = new Event('Model.Resource.afterSoftDelete', $resource);
$this->getEventManager()->dispatch($event);
return true;
}
/**
* Remove the resource associated data for the users who lost access to the resource.
*
* @param string $resourceId The resource identifier the users lost the access to
* @param array $usersId The list of users who lost access to the resource
* @return void
*/
public function deleteLostAccessAssociatedData(string $resourceId, array $usersId = []): void
{
if (empty($usersId)) {
return;
}
$Favorites = TableRegistry::getTableLocator()->get('Favorites');
$Favorites->deleteAll([
'foreign_key' => $resourceId,
'user_id IN' => $usersId,
]);
}
/**
* Soft delete a list of resources by Ids
*
* @param array $resourceIds uuid of Resources
* @param bool $cascade true
* @return void
*/
public function softDeleteAll(array $resourceIds, bool $cascade = true): void
{
// CakePHP will return an error on the coming query if $resourceIds is empty
if (empty($resourceIds)) {
return;
}
$this->updateAll([
'deleted' => true,
'username' => null,
'uri' => null,
'description' => null,
], ['id IN' => $resourceIds]);
if ($cascade) {
$Favorites = TableRegistry::getTableLocator()->get('Favorites');
$Favorites->deleteAll(['foreign_key IN' => $resourceIds]);
$Secrets = TableRegistry::getTableLocator()->get('Secrets');
$Secrets->deleteAll(['resource_id IN' => $resourceIds]);
$Permissions = TableRegistry::getTableLocator()->get('Permissions');
$Permissions->deleteAll(['aco_foreign_key IN' => $resourceIds]);
}
}
/**
* Cleanup resource where resource type id is null
* Set it to the default
*
* @param bool $dryRun false
* @return int number of affected rows.
*/
public function cleanupMissingResourceTypeId(bool $dryRun = false): int
{
$condition = ['resource_type_id IS' => null];
if ($dryRun) {
return $this->find()
->where($condition)
->count();
}
return $this->updateAll(['resource_type_id' => ResourceTypesTable::getDefaultTypeId()], $condition);
}
}
?>
Did this file decode correctly?
Original Code
<?php
declare(strict_types=1);
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 2.0.0
*/
namespace App\Model\Table;
use App\Model\Entity\Permission;
use App\Model\Entity\Resource;
use App\Model\Entity\Role;
use App\Model\Rule\IsNotSoftDeletedRule;
use App\Model\Traits\Resources\ResourcesFindersTrait;
use App\Model\Validation\DateTime\IsParsableDateTimeValidationRule;
use App\Utility\Application\FeaturePluginAwareTrait;
use App\Utility\UuidFactory;
use Cake\Event\Event;
use Cake\I18n\FrozenTime;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Validation\Validation;
use Cake\Validation\Validator;
use Passbolt\ResourceTypes\Model\Table\ResourceTypesTable;
/**
* Resources Model
*
* @property \App\Model\Table\UsersTable&\Cake\ORM\Association\HasOne $Creator
* @property \App\Model\Table\UsersTable&\Cake\ORM\Association\HasOne $Modifier
* @property \App\Model\Table\SecretsTable&\Cake\ORM\Association\HasMany $Secrets
* @property \App\Model\Table\PermissionsTable&\Cake\ORM\Association\HasMany $Permissions
* @method \App\Model\Entity\Resource get($primaryKey, $options = [])
* @method \App\Model\Entity\Resource[] newEntities(array $data, array $options = [])
* @method \App\Model\Entity\Resource|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \App\Model\Entity\Resource patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* @method \App\Model\Entity\Resource[] patchEntities(iterable $entities, array $data, array $options = [])
* @method \App\Model\Entity\Resource findOrCreate($search, ?callable $callback = null, $options = [])
* @mixin \Cake\ORM\Behavior\TimestampBehavior
* @property \App\Model\Table\FavoritesTable&\Cake\ORM\Association\HasOne $Favorites
* @property \App\Model\Table\PermissionsTable&\Cake\ORM\Association\HasOne $Permission
* @property \Passbolt\ResourceTypes\Model\Table\ResourceTypesTable&\Cake\ORM\Association\BelongsTo $ResourceTypes
* @property \Passbolt\Log\Model\Table\EntitiesHistoryTable&\Cake\ORM\Association\BelongsTo $EntitiesHistory
* @method \App\Model\Entity\Resource newEmptyEntity()
* @method \App\Model\Entity\Resource newEntity(array $data, array $options = [])
* @method \App\Model\Entity\Resource saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface>|false saveMany(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface> saveManyOrFail(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface>|false deleteMany(iterable $entities, $options = [])
* @method iterable<\App\Model\Entity\Resource>|iterable<\Cake\Datasource\EntityInterface> deleteManyOrFail(iterable $entities, $options = [])
* @method \Cake\ORM\Query findByIdAndDeleted(string $id, bool $delete)
*/
class ResourcesTable extends Table
{
use ResourcesFindersTrait;
use FeaturePluginAwareTrait;
public const DESCRIPTION_MAX_LENGTH = 10000;
public const NAME_MAX_LENGTH = 255;
public const URI_MAX_LENGTH = 1024;
public const USERNAME_MAX_LENGTH = 255;
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('resources');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasOne('Creator', [
'className' => 'Users',
'bindingKey' => 'created_by',
'foreignKey' => 'id',
]);
$this->hasOne('Favorites', [
'foreignKey' => 'foreign_key',
]);
$this->hasOne('Modifier', [
'className' => 'Users',
'bindingKey' => 'modified_by',
'foreignKey' => 'id',
]);
$this->hasOne('Permission', [
'className' => 'Permissions',
'foreignKey' => 'aco_foreign_key',
'joinType' => 'INNER',
]);
$this->hasMany('Permissions', [
'foreignKey' => 'aco_foreign_key',
'saveStrategy' => 'replace',
// Important so that we can track the delete event and log it.
'cascadeCallbacks' => true,
]);
$this->hasMany('Secrets', [
'foreignKey' => 'resource_id',
'saveStrategy' => 'replace',
]);
$this->belongsTo('ResourceTypes');
}
/**
* Default validation rules.
*
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->uuid('id', __('The identifier should be a valid UUID.'))
->allowEmptyString('id', __('The identifier should not be empty.'), 'create');
$validator
->utf8Extended('name', __('The name should be a valid UTF8 string.'))
->maxLength(
'name',
self::NAME_MAX_LENGTH,
__('The name length should be maximum {0} characters.', self::NAME_MAX_LENGTH)
)
->requirePresence('name', 'create', __('A name is required.'))
->allowEmptyString('name', __('The name should not be empty.'), false);
$validator
->utf8Extended('username', __('The username should be a valid UTF8 string.'))
->maxLength(
'username',
self::USERNAME_MAX_LENGTH,
__('The username length should be maximum {0} characters.', self::USERNAME_MAX_LENGTH)
)
->allowEmptyString('username');
$validator
->utf8('uri', __('The uri should be a valid BMP-UTF8 string.'))
->maxLength(
'uri',
self::URI_MAX_LENGTH,
__('The uri length should be maximum {0} characters.', self::URI_MAX_LENGTH)
)
->allowEmptyString('uri');
$validator
->utf8Extended('description', __('The description should be a valid UTF8 string.'))
->maxLength(
'description',
self::DESCRIPTION_MAX_LENGTH,
__('The description length should be maximum {0} characters.', self::DESCRIPTION_MAX_LENGTH)
)
->allowEmptyString('description');
$validator
->boolean('deleted', __('The deleted status should be a valid boolean.'))
->allowEmptyString('deleted', __('The deleted status should not be empty'), false);
$validator
->allowEmptyDateTime('expired')
->add('expired', 'expired', new IsParsableDateTimeValidationRule());
$validator
->uuid('created_by', __('The identifier of the user who created the resource should be a valid UUID.'))
->requirePresence(
'created_by',
'create',
__('The identifier of the user who created the resource is required.')
)
->allowEmptyString(
'created_by',
__('The identifier of the user who created the resource should not be empty.'),
false
);
$validator
->uuid(
'modified_by',
__('The identifier of the user who last modified the resource should be a valid UUID.')
)
->requirePresence(
'modified_by',
'create',
__('The identifier of the user who last modified the resource required.')
)
->allowEmptyString(
'modified_by',
__('The identifier of the user who last modified the resource should not be empty.'),
false
);
$validator
->uuid('resource_type_id', __('The resource type identifier should be a valid UUID.'))
->requirePresence('resource_type_id', 'create', __('A resource type identifier is required.'));
// Associated fields
$validator
->requirePresence('permissions', 'create', __('The permissions are required.'))
// @todo Secrets is an array
->allowEmptyString('permissions', __('The permissions should not be empty.'), false)
->hasAtMost(
'permissions',
1,
__('The permissions should contain only the permission of the owner.'),
'create'
);
$validator
->requirePresence('secrets', 'create', __('The owner secret is required.'))
// @todo Secrets is an array
->allowEmptyString('secrets', __('The secrets should not be empty.'), false)
->hasAtMost('secrets', 1, __('The secrets should contain only the secret of the owner.'), 'create');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
// Create and Update rules
$rules->add($rules->existsIn('resource_type_id', 'ResourceTypes'), 'resource_type_exists', [
'message' => __('The resource type does not exist.'),
]);
// Create rules.
$rules->addCreate([$this, 'isOwnerPermissionProvidedRule'], 'owner_permission_provided', [
'errorField' => 'permissions',
'message' => __('The permissions should contain the owner permission.'),
]);
$rules->addCreate([$this, 'isOwnerSecretProvidedRule'], 'owner_secret_provided', [
'errorField' => 'secrets',
'message' => __('The secrets should contain the owner secret.'),
]);
// Update rules.
$rules->addUpdate([$this, 'isSecretsProvidedRule'], 'secrets_provided', [
'errorField' => 'secrets',
'message' => __('The secrets should contain the secrets of all the users having access to the resource.'),
]);
$rules->addUpdate(new IsNotSoftDeletedRule(), 'resource_is_not_soft_deleted', [
'table' => 'Resources',
'errorField' => 'id',
'message' => __('The resource does not exist.'),
]);
$rules->addUpdate([$this, 'isOwnerPermissionProvidedRule'], 'at_least_one_owner', [
'errorField' => 'permissions',
'message' => __('The permissions should contain at least the owner permission.'),
]);
return $rules;
}
/**
* @param \Cake\Event\Event $event event
* @param \ArrayObject $data data
* @param \ArrayObject $options options
* @return void
* @deprecated can be removed once isDescriptionEmptyOnPasswordAndDescriptionResourceType is a rule
*/
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options): void
{
if (!$this->isDescriptionEmptyOnPasswordAndDescriptionResourceType($data)) {
$data['description'] = null;
}
if (isset($data['expired']) && !empty($data['expired'])) {
// Parse the expired date into a time object
try {
$data['expired'] = FrozenTime::parse($data['expired']);
} catch (\Throwable $e) {
// If the expired date cannot be parsed, let the validation
// handle the fail
}
}
}
/**
* Validate that the entity has at least one owner
*
* @param \App\Model\Entity\Resource $entity The entity that will be created or updated.
* @param array|null $options options
* @return bool
*/
public function isOwnerPermissionProvidedRule(Resource $entity, ?array $options = [])
{
if (isset($entity->permissions)) {
$found = Hash::extract($entity->permissions, '{n}[type=' . Permission::OWNER . ']');
if (empty($found)) {
return false;
}
}
return true;
}
/**
* Validate that the entity description is empty if the resource type is password and description
* This can be refactored as a rule after 4.0.0
*
* @param \ArrayObject $data data
* @return bool
* @todo refactor this as a rule after 4.0.0
*/
public function isDescriptionEmptyOnPasswordAndDescriptionResourceType(\ArrayObject $data): bool
{
$resourceTypeId = $data['resource_type_id'] ?? null;
$description = $data['description'] ?? null;
$resourceTypePasswordAndDescriptionId = UuidFactory::uuid('resource-types.id.password-and-description');
$isResourceTypePasswordAndDescription = $resourceTypeId === $resourceTypePasswordAndDescriptionId;
if ($isResourceTypePasswordAndDescription && !empty($description)) {
return false;
}
return true;
}
/**
* Validate that a resource can be created only if the secret of the owner is provided.
*
* @param \App\Model\Entity\Resource $entity The entity that will be created.
* @param array|null $options options
* @return bool
*/
public function isOwnerSecretProvidedRule(Resource $entity, ?array $options = [])
{
return $entity->secrets[0]->user_id === $entity->created_by;
}
/**
* Validate that the secrets of all the allowed users are provided if the secret changed.
*
* @param \App\Model\Entity\Resource $entity The entity that will be created.
* @param array|null $options options
* @return bool
*/
public function isSecretsProvidedRule(Resource $entity, ?array $options = [])
{
// Secrets are not required to update a resource, but if provided check that the list of secrets correspond
// only to the users who have access to the resource.
if (!isset($entity->secrets)) {
return true;
}
// Retrieve the users who are allowed to access the resource.
/** @var \App\Model\Table\UsersTable $Users */
$Users = TableRegistry::getTableLocator()->get('Users');
$usersFindOptions['filter']['has-access'] = [$entity->id];
$allowedUsersIds = $Users->findIndex(Role::USER, $usersFindOptions)
->all()
->extract('id')
->toArray();
// Extract the users for whom the secrets will be updated.
$secretsUsersIds = Hash::extract($entity->secrets, '{n}.user_id');
// If the list of secrets does not correspond to the list of users who have access to the resource,
// do not validate.
if (
count($secretsUsersIds) != count($allowedUsersIds)
|| !empty(array_diff($allowedUsersIds, $secretsUsersIds))
) {
return false;
}
return true;
}
/**
* Soft delete a resource.
*
* @param string $userId The user who perform the delete.
* @param \App\Model\Entity\Resource $resource The resource to delete.
* @throws \InvalidArgumentException if the user id is not a uuid
* @return bool true if success
*/
public function softDelete(string $userId, Resource $resource): bool
{
// The softDelete will perform an update to the entity to soft delete it.
if (!Validation::uuid($userId)) {
throw new \InvalidArgumentException('The user identifier should be a valid UUID.');
}
if ($resource->deleted) {
$resource->setError('deleted', [
'is_not_soft_deleted' => __('The resource should not be already soft deleted.'),
]);
return false;
}
$acoType = PermissionsTable::RESOURCE_ACO;
if (!$this->Permissions->hasAccess($acoType, $resource->id, $userId, Permission::UPDATE)) {
$resource->setError('id', [
'has_access' => __('The user cannot delete this resource.'),
]);
return false;
}
// Patch the entity.
$data = [
'deleted' => true,
'modified_by' => $userId,
'secrets' => [],
// cleanup sensitive data
'username' => null,
'uri' => null,
'description' => null,
];
$patchOptions = [
'accessibleFields' => [
'username' => true,
'uri' => true,
'description' => true,
'deleted' => true,
'secrets' => true,
'modified' => true,
'modified_by' => true,
],
];
$this->patchEntity($resource, $data, $patchOptions);
if ($resource->getErrors()) {
return false;
}
// Soft delete the resource.
$this->save($resource, ['checkRules' => false]);
if ($resource->getErrors()) {
return false;
}
// Remove all the associated permissions.
$this->getAssociation('Permissions')
->deleteAll(['Permissions.aco_foreign_key' => $resource->id]);
// Remove all the associated favorites.
$this->getAssociation('Favorites')
->deleteAll(['Favorites.foreign_key' => $resource->id]);
// Notify other components about the resource soft delete.
$event = new Event('Model.Resource.afterSoftDelete', $resource);
$this->getEventManager()->dispatch($event);
return true;
}
/**
* Remove the resource associated data for the users who lost access to the resource.
*
* @param string $resourceId The resource identifier the users lost the access to
* @param array $usersId The list of users who lost access to the resource
* @return void
*/
public function deleteLostAccessAssociatedData(string $resourceId, array $usersId = []): void
{
if (empty($usersId)) {
return;
}
$Favorites = TableRegistry::getTableLocator()->get('Favorites');
$Favorites->deleteAll([
'foreign_key' => $resourceId,
'user_id IN' => $usersId,
]);
}
/**
* Soft delete a list of resources by Ids
*
* @param array $resourceIds uuid of Resources
* @param bool $cascade true
* @return void
*/
public function softDeleteAll(array $resourceIds, bool $cascade = true): void
{
// CakePHP will return an error on the coming query if $resourceIds is empty
if (empty($resourceIds)) {
return;
}
$this->updateAll([
'deleted' => true,
'username' => null,
'uri' => null,
'description' => null,
], ['id IN' => $resourceIds]);
if ($cascade) {
$Favorites = TableRegistry::getTableLocator()->get('Favorites');
$Favorites->deleteAll(['foreign_key IN' => $resourceIds]);
$Secrets = TableRegistry::getTableLocator()->get('Secrets');
$Secrets->deleteAll(['resource_id IN' => $resourceIds]);
$Permissions = TableRegistry::getTableLocator()->get('Permissions');
$Permissions->deleteAll(['aco_foreign_key IN' => $resourceIds]);
}
}
/**
* Cleanup resource where resource type id is null
* Set it to the default
*
* @param bool $dryRun false
* @return int number of affected rows.
*/
public function cleanupMissingResourceTypeId(bool $dryRun = false): int
{
$condition = ['resource_type_id IS' => null];
if ($dryRun) {
return $this->find()
->where($condition)
->count();
}
return $this->updateAll(['resource_type_id' => ResourceTypesTable::getDefaultTypeId()], $condition);
}
}
Function Calls
None |
Stats
MD5 | 61e6838e6b089aea04b58611a5d39155 |
Eval Count | 0 |
Decode Time | 82 ms |