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\Traits\Permissions;

use App\Model\Entity\Permission;
use App\Model\Table\AvatarsTable;
use App\Model\Table\PermissionsTable;
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\Http\Exception\BadRequestException;
use Cake\ORM\Query;
use Cake\Utility\Hash;
use Cake\Validation\Validation;
use InvalidArgumentException;

trait PermissionsFindersTrait
{
    /**
     * Find the highest permission an aro or the groups the aro is member of could have for a given aco.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string|\Cake\Database\Expression\IdentifierExpression $acoForeignKey The target aco. By instance resource or folder id.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @return \Cake\ORM\Query
     */
    public function findHighestByAcoAndAro(string $acoType, $acoForeignKey, string $aroForeignKey): Query
    {
        return $this->findAllByAro($acoType, $aroForeignKey, ['checkGroupsUsers' => true])
            ->where(['Permissions.aco_foreign_key' => $acoForeignKey])
            ->orderDesc('Permissions.type')
            ->limit(1);
    }

    /**
     * Returns a query retrieving data for aco permissions view
     *
     * @param string $aco The target aco id. By instance a resource or a folder id.
     * @param array|null $options options
     * @throws \InvalidArgumentException if the userId parameter is not a valid uuid.
     * @throws \InvalidArgumentException if the resourceId parameter is not a valid uuid.
     * @return \Cake\ORM\Query
     */
    public function findViewAcoPermissions(string $aco, ?array $options = []): Query
    {
        if (!Validation::uuid($aco)) {
            throw new InvalidArgumentException('The access control object identifier should be a valid UUID.');
        }

        $query = $this->find()
            ->where(['Permissions.aco_foreign_key' => $aco]);

        // If contains group.
        if (isset($options['contain']['group'])) {
            $query->contain('Groups');
        }

        // If contains user.
        if (isset($options['contain']['user'])) {
            $query->contain('Users');
        }

        // If contains user profile.
        if (isset($options['contain']['user.profile'])) {
            $query->contain([
                'Users' => [
                    'Profiles' =>
                        AvatarsTable::addContainAvatar(),
                ],
            ]);
        }

        return $query;
    }

    /**
     * Returns a query retrieving the permissions an aro have.
     *
     * The $checkGroupsUsers will also return the permissions inherited from the groups the aro is member of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @param array|null $options options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @return \Cake\ORM\Query
     * @throws \Cake\Http\Exception\BadRequestException if the aro foreign key is not a valid UUID
     */
    public function findAllByAro(string $acoType, string $aroForeignKey, ?array $options = []): Query
    {
        if (!Validation::uuid($aroForeignKey)) {
            throw new BadRequestException(__('The identifier should be a valid UUID.'));
        }
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        // Retrieve also the permissions for the groups a user is member of.
        if ($checkGroupsUsers) {
            $aroForeignKeys = $this->Groups->GroupsUsers->find()
                ->select('group_id')
                ->where(['user_id' => $aroForeignKey])
                ->epilog('UNION SELECT :aroForeignKey')
                ->bind(':aroForeignKey', $aroForeignKey);
        } else {
            $aroForeignKeys = [$aroForeignKey];
        }

        return $this->find()
            ->where([
                'Permissions.aco' => $acoType,
                'Permissions.aro_foreign_key IN' => $aroForeignKeys,
            ]);
    }

    /**
     * Returns a query retrieving the acos (resources or folders) that are shared with someone else that the given aco
     * (user or group). By instance, it is useful to know which resources ownership need to be transferred when deleting
     * a user.
     *
     * The options $checkGroupsUsers option will also take in account the groups the aro is sole manager that could be
     * sole owner of shared acos.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array|null $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @throws \InvalidArgumentException if the user id is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findSharedAcosByAroIsSoleOwner(string $acoType, string $aro, ?array $options = []): Query
    {
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        if (!Validation::uuid($aro)) {
            throw new InvalidArgumentException('The user identifier should be a valid UUID.');
        }

        if ($checkGroupsUsers) {
            // R = All the shared resources that are only owned by the user given in parameter or owned by non empty groups he is sole manager of
            // If the user is deleted these resources will require their permissions to be updated to not be left without OWNER.
            //
            // Details:
            // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
            // USER_AND_SOLE_MANAGER_GROUPS, is a set of AROs represented by the user given in parameter and the groups he is sole manager
            // USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS, is a set of AROs represented by the non empty groups the user is sole manager
            // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
            // ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS, all the ACOS that are only owned by the user and the groups he is sole manager
            // ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS, all the ACOS that are owned by the user and the non empty groups he is sole manager
            // ACOS_ONLY_ACCESSIBLE_BY_USER, all the ACOS that are only accessible by the user and the groups he is the only member
            // R = ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS - ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS - ACOS_ONLY_ACCESSIBLE_BY_USER

            // (USER_AND_SOLE_MANAGER_GROUPS)
            $groupsSoleManager = $this->Groups->GroupsUsers->findGroupsWhereUserIsSoleManager($aro)
                ->all()
                ->extract('group_id')->toArray();
            // (R = ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS)
            $aros = [$aro];
            $aros = array_merge($aros, $groupsSoleManager);
            $query = $this->findAcosByArosAreSoleOwner($acoType, $aros);

            // (USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
            $nonEmptyGroupsSoleManager = $this->Groups->GroupsUsers->findNonEmptyGroupsWhereUserIsSoleManager($aro)
                ->all()
                ->extract('group_id')
                ->toArray();
            if (!empty($nonEmptyGroupsSoleManager)) {
                // (ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
                $acosOnlyOwnedByUsersAndSoleManagerOfNonEmptyGroups = $this->find()
                    ->select('aco_foreign_key')->distinct()
                    ->where([
                        'aco' => $acoType,
                        'type' => Permission::OWNER,
                        'aro_foreign_key IN' => $nonEmptyGroupsSoleManager,
                    ]);

                // (R = R - ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
                $query->where(['aco_foreign_key NOT IN' => $acosOnlyOwnedByUsersAndSoleManagerOfNonEmptyGroups]);
            }

            // (ACOS_ONLY_ACCESSIBLE_BY_USER)
            $subquery = $this->findAcosOnlyAroCanAccess($acoType, $aro, ['checkGroupsUsers' => $checkGroupsUsers]);
            // (R = R - ACOS_ONLY_ACCESSIBLE_BY_USER)
            $query->where([
                'aco' => $acoType,
                'aco_foreign_key NOT IN' => $subquery,
            ]);
        } else {
            $aros = [$aro];
            // (R = ACOS_ONLY_OWNED_BY_USER)
            $query = $this->findAcosByArosAreSoleOwner($acoType, $aros);
            // (ACOS_ONLY_ACCESSIBLE_BY_USER)
            $subquery = $this->findAcosOnlyAroCanAccess($acoType, $aro, ['checkGroupsUsers' => $checkGroupsUsers]);
            // (R = R - ACOS_ONLY_ACCESSIBLE_BY_USER)
            $query->where([
                'aco' => $acoType,
                'aco_foreign_key NOT IN' => $subquery,
            ]);
        }

        return $query;
    }

    /**
     * Returns a query retrieving the acos (resources or folders) that at least one of the given aros (users or groups)
     * is sole owner.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param array $aros An array of aro id. Composed of users or groups ids.
     * @throw \InvalidArgumentException if the aros parameter contains not only uuid value.
     * @return \Cake\ORM\Query
     */
    public function findAcosByArosAreSoleOwner(string $acoType, array $aros)
    {
        foreach ($aros as $aro) {
            if (!Validation::uuid($aro)) {
                throw new InvalidArgumentException('The access request object identifier should be a valid UUID.');
            }
        }

        // R = All the resources that are only owned by the user given in parameter or owned by non empty groups he is sole manager of.
        //
        // Details:
        // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
        // USER_AND_GROUPS, is a set of AROs represented by the user and groups given as parameter
        // OTHER_USERS_AND_GROUPS, is a set of AROs represented by the users and groups which are not USER_AND_GROUPS
        // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
        // ACOS_OWNED_BY_USERS_AND_GROUPS, is the set of AROS that are owned by the USERS_AND_GROUPS
        // ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS, is the set of AROS that are owned by the OTHER_USERS_AND_GROUPS
        // R = ACOS_OWNED_BY_USERS_AND_GROUPS - ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS, is the set of ACOS only owned by USERS_AND_GROUPS

        // (ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key NOT IN (USER_AND_GROUPS)
        // AND type = OWNER
        $acosOwnedByOtherUsersAndGroups = $this->find()
            ->select(['aco_foreign_key'])->distinct()
            ->where([
                'aco' => $acoType,
                'aro_foreign_key NOT IN' => $aros,
                'type' => Permission::OWNER,
            ]);

        // (R)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key IN (USER_AND_GROUPS)
        // AND type = OWNER
        // AND aco_foreign_key NOT IN (ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS)
        return $this->find()
            ->select(['aco_foreign_key'])->distinct()
            // ACOS_OWNED_BY_USERS_AND_GROUPS
            ->where([
                'aco' => $acoType,
                'aro_foreign_key IN' => $aros,
                'type' => Permission::OWNER,
            ])
            // ACOS_OWNED_BY_USERS_AND_GROUPS - ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS
            ->where(['aco_foreign_key NOT IN' => $acosOwnedByOtherUsersAndGroups]);
    }

    /**
     * Returns a query retrieving the acos (resources or folders) a given aro (user or group) is the only one to have
     * access.
     *
     * The $checkGroupsUsers options will also check the groups the aro is member of. The returned query will return
     * also the acos that are accessible only by the groups the aro is the only member of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of.
     * ]
     * @throws \InvalidArgumentException if the aro id is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findAcosOnlyAroCanAccess(string $acoType, string $aro, ?array $options = [])
    {
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        // R = All the resources that are only accessible by a list of users and/or groups.
        //
        // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
        // USER_AND_GROUPS, is a set of AROs represented by the user and groups given as parameter
        // OTHER_USERS_AND_GROUPS, is a set of AROs represented by the users and groups which are not USER_AND_GROUPS
        // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
        // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS, is the set of AROS that are accessible by USERS_AND_GROUPS
        // ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS, is the set of AROS that are accessible by OTHER_USERS_AND_GROUPS
        // R = ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS - ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS, is the set of ACOS only accessible by USERS_AND_GROUPS

        // USER_AND_GROUPS
        $aros = [$aro];
        if ($checkGroupsUsers) {
            $groups = $this->Groups->GroupsUsers->findGroupsWhereUserOnlyMember($aro)
                ->all()
                ->extract('group_id')->toArray();
            $aros = array_merge($aros, $groups);
        }

        // (ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key NOT IN (USER_AND_GROUPS)
        $acosAccessibleByOtherUsersAndGroups = $this->find()
            ->select(['aco_foreign_key'])
            ->where([
                'aco' => $acoType,
                'aro_foreign_key NOT IN' => $aros,
            ]);

        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key IN (USER_AND_GROUPS)
        // AND aco_foreign_key NOT IN (ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS)
        return $this->find()
            ->select(['aco_foreign_key'])->distinct()
            // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS
            ->where([
                'aco' => $acoType,
                'aro_foreign_key IN' => $aros,
            ])
            // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS - ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS
            ->where(['aco_foreign_key NOT IN' => $acosAccessibleByOtherUsersAndGroups]);
    }

    /**
     * Find access differences between a group and a user.
     * Return only the accesses that are found in the group accesses but not in the user accesses, such as array_diff
     * will do.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $groupId The group identifier.
     * @param string $userId The user identifier.
     * @return \Cake\ORM\Query
     */
    public function findAcosAccessesDiffBetweenGroupAndUser(string $acoType, string $groupId, string $userId): Query
    {
        // R = All the resources or folders that are only accessible by a group and not accessible by a user

        // Details:
        // ACOS_GROUP_ACCESS, is the set of ACOS that a group can access
        // ACOS_USER_ACCESS, is the set of ACOS that a user can access
        // R = ACOS_GROUP_ACCESS - ACOS_USER_ACCESS

        // ACOS_USER_ACCESS
        $remainAccessAcoForeignKeysQuery = $this->findAllByAro($acoType, $userId, ['checkGroupsUsers' => true])
            ->select('aco_foreign_key');

        // R = ACOS_GROUP_ACCESS - ACOS_USER_ACCESS
        return $this->findAllByAro($acoType, $groupId)
            ->select('aco_foreign_key')
            ->where([
                'aco_foreign_key NOT IN' => $remainAccessAcoForeignKeysQuery,
            ]);
    }

    /**
     * Find access differences between a group and multiple users.
     * Return only the accesses that are found in the group accesses but not in the user accesses, such as array_diff
     * would do.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $groupId The group identifier.
     * @param string[] $usersIds The user identifier.
     * @return \Cake\ORM\Query
     */
    public function findAcosAccessesDiffBetweenGroupAndUsers(string $acoType, string $groupId, array $usersIds): Query
    {
        // @todo to document

        $directUsersAccessesQuery = $this->find()
            ->select([
                'user_id' => 'aro_foreign_key',
                'resource_id' => 'aco_foreign_key',
            ])
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::USER_ARO,
                'aro_foreign_key IN' => $usersIds,
            ]);

        $inheritedUsersAccessesExcludingGroupQuery = $this->find()
            ->select([
                'user_id' => 'groups_users.user_id',
                'resource_id' => 'aco_foreign_key',
            ])
            ->leftJoin('groups_users', 'aro_foreign_key = group_id')
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::GROUP_ARO,
                'groups_users.user_id IN' => $usersIds,
                'groups_users.group_id <>' => $groupId,
            ]);

        $groupUsersAccessesQuery = $this->find()
            ->select([
                'user_id' => 'groups_users.user_id',
                'resource_id' => 'aco_foreign_key',
            ])
            ->leftJoin('groups_users', 'aro_foreign_key = group_id')
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::GROUP_ARO,
                'aro_foreign_key' => $groupId,
            ]);

        return $groupUsersAccessesQuery
            ->leftJoin(['DirectUsersAccesses' => $directUsersAccessesQuery], [
                'DirectUsersAccesses.resource_id' => new IdentifierExpression('Permissions.aco_foreign_key'),
                'DirectUsersAccesses.user_id' => new IdentifierExpression('groups_users.user_id'),
            ])
            ->leftJoin(['InheritedUsersAccesses' => $inheritedUsersAccessesExcludingGroupQuery], [
                'InheritedUsersAccesses.resource_id' => new IdentifierExpression('Permissions.aco_foreign_key'),
                'InheritedUsersAccesses.user_id' => new IdentifierExpression('groups_users.user_id'),
            ])
            ->where(function (QueryExpression $exp) {
                return $exp->isNull('DirectUsersAccesses.resource_id')
                    ->isNull('InheritedUsersAccesses.resource_id');
            });
    }

    /**
     * Returns a query retrieving the acos (resources or folders) a given aro (user or group) is owner of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @throws \InvalidArgumentException if the aro foreign key is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findAcosByAroIsOwner(string $acoType, string $aro, ?array $options = [])
    {
        return $this->findAllByAro($acoType, $aro, $options)
            ->where(['Permissions.type' => Permission::OWNER])
            ->select('aco_foreign_key')->distinct();
    }

    /**
     * Check that an aro has access to an aco.
     *
     * @param string $acoType The target aco type. By instance a Resource or a Folder.
     * @param string $acoForeignKey The target aco id. By instance a resource or a folder id.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @param int $permissionType The minimum permission type
     * @throws \InvalidArgumentException if the $aco parameter is not a valid uuid.
     * @throws \InvalidArgumentException if the $aro parameter is not a valid uuid.
     * @return bool
     */
    public function hasAccess(
        string $acoType,
        string $acoForeignKey,
        string $aroForeignKey,
        ?int $permissionType = null
    ): bool {
        if (!Validation::uuid($acoForeignKey)) {
            throw new InvalidArgumentException('The aco parameter should be a valid UUID.');
        }
        if (!Validation::uuid($aroForeignKey)) {
            throw new InvalidArgumentException('The aro parameter should be a valid UUID.');
        }
        $permissionType = $permissionType ?? Permission::READ;

        if (!$this->isValidPermissionType($permissionType)) {
            $msg = 'The permission type should be in the list of allowed permission type.';
            throw new InvalidArgumentException($msg);
        }
        $query = $this->findHighestByAcoAndAro($acoType, $acoForeignKey, $aroForeignKey)
            ->where(['Permissions.type >=' => $permissionType]);

        return $query->count() !== 0;
    }
}
 ?>

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\Traits\Permissions;

use App\Model\Entity\Permission;
use App\Model\Table\AvatarsTable;
use App\Model\Table\PermissionsTable;
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\Http\Exception\BadRequestException;
use Cake\ORM\Query;
use Cake\Utility\Hash;
use Cake\Validation\Validation;
use InvalidArgumentException;

trait PermissionsFindersTrait
{
    /**
     * Find the highest permission an aro or the groups the aro is member of could have for a given aco.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string|\Cake\Database\Expression\IdentifierExpression $acoForeignKey The target aco. By instance resource or folder id.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @return \Cake\ORM\Query
     */
    public function findHighestByAcoAndAro(string $acoType, $acoForeignKey, string $aroForeignKey): Query
    {
        return $this->findAllByAro($acoType, $aroForeignKey, ['checkGroupsUsers' => true])
            ->where(['Permissions.aco_foreign_key' => $acoForeignKey])
            ->orderDesc('Permissions.type')
            ->limit(1);
    }

    /**
     * Returns a query retrieving data for aco permissions view
     *
     * @param string $aco The target aco id. By instance a resource or a folder id.
     * @param array|null $options options
     * @throws \InvalidArgumentException if the userId parameter is not a valid uuid.
     * @throws \InvalidArgumentException if the resourceId parameter is not a valid uuid.
     * @return \Cake\ORM\Query
     */
    public function findViewAcoPermissions(string $aco, ?array $options = []): Query
    {
        if (!Validation::uuid($aco)) {
            throw new InvalidArgumentException('The access control object identifier should be a valid UUID.');
        }

        $query = $this->find()
            ->where(['Permissions.aco_foreign_key' => $aco]);

        // If contains group.
        if (isset($options['contain']['group'])) {
            $query->contain('Groups');
        }

        // If contains user.
        if (isset($options['contain']['user'])) {
            $query->contain('Users');
        }

        // If contains user profile.
        if (isset($options['contain']['user.profile'])) {
            $query->contain([
                'Users' => [
                    'Profiles' =>
                        AvatarsTable::addContainAvatar(),
                ],
            ]);
        }

        return $query;
    }

    /**
     * Returns a query retrieving the permissions an aro have.
     *
     * The $checkGroupsUsers will also return the permissions inherited from the groups the aro is member of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @param array|null $options options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @return \Cake\ORM\Query
     * @throws \Cake\Http\Exception\BadRequestException if the aro foreign key is not a valid UUID
     */
    public function findAllByAro(string $acoType, string $aroForeignKey, ?array $options = []): Query
    {
        if (!Validation::uuid($aroForeignKey)) {
            throw new BadRequestException(__('The identifier should be a valid UUID.'));
        }
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        // Retrieve also the permissions for the groups a user is member of.
        if ($checkGroupsUsers) {
            $aroForeignKeys = $this->Groups->GroupsUsers->find()
                ->select('group_id')
                ->where(['user_id' => $aroForeignKey])
                ->epilog('UNION SELECT :aroForeignKey')
                ->bind(':aroForeignKey', $aroForeignKey);
        } else {
            $aroForeignKeys = [$aroForeignKey];
        }

        return $this->find()
            ->where([
                'Permissions.aco' => $acoType,
                'Permissions.aro_foreign_key IN' => $aroForeignKeys,
            ]);
    }

    /**
     * Returns a query retrieving the acos (resources or folders) that are shared with someone else that the given aco
     * (user or group). By instance, it is useful to know which resources ownership need to be transferred when deleting
     * a user.
     *
     * The options $checkGroupsUsers option will also take in account the groups the aro is sole manager that could be
     * sole owner of shared acos.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array|null $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @throws \InvalidArgumentException if the user id is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findSharedAcosByAroIsSoleOwner(string $acoType, string $aro, ?array $options = []): Query
    {
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        if (!Validation::uuid($aro)) {
            throw new InvalidArgumentException('The user identifier should be a valid UUID.');
        }

        if ($checkGroupsUsers) {
            // R = All the shared resources that are only owned by the user given in parameter or owned by non empty groups he is sole manager of
            // If the user is deleted these resources will require their permissions to be updated to not be left without OWNER.
            //
            // Details:
            // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
            // USER_AND_SOLE_MANAGER_GROUPS, is a set of AROs represented by the user given in parameter and the groups he is sole manager
            // USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS, is a set of AROs represented by the non empty groups the user is sole manager
            // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
            // ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS, all the ACOS that are only owned by the user and the groups he is sole manager
            // ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS, all the ACOS that are owned by the user and the non empty groups he is sole manager
            // ACOS_ONLY_ACCESSIBLE_BY_USER, all the ACOS that are only accessible by the user and the groups he is the only member
            // R = ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS - ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS - ACOS_ONLY_ACCESSIBLE_BY_USER

            // (USER_AND_SOLE_MANAGER_GROUPS)
            $groupsSoleManager = $this->Groups->GroupsUsers->findGroupsWhereUserIsSoleManager($aro)
                ->all()
                ->extract('group_id')->toArray();
            // (R = ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_GROUPS)
            $aros = [$aro];
            $aros = array_merge($aros, $groupsSoleManager);
            $query = $this->findAcosByArosAreSoleOwner($acoType, $aros);

            // (USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
            $nonEmptyGroupsSoleManager = $this->Groups->GroupsUsers->findNonEmptyGroupsWhereUserIsSoleManager($aro)
                ->all()
                ->extract('group_id')
                ->toArray();
            if (!empty($nonEmptyGroupsSoleManager)) {
                // (ACOS_ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
                $acosOnlyOwnedByUsersAndSoleManagerOfNonEmptyGroups = $this->find()
                    ->select('aco_foreign_key')->distinct()
                    ->where([
                        'aco' => $acoType,
                        'type' => Permission::OWNER,
                        'aro_foreign_key IN' => $nonEmptyGroupsSoleManager,
                    ]);

                // (R = R - ONLY_OWNED_BY_USER_AND_SOLE_MANAGER_NON_EMPTY_GROUPS)
                $query->where(['aco_foreign_key NOT IN' => $acosOnlyOwnedByUsersAndSoleManagerOfNonEmptyGroups]);
            }

            // (ACOS_ONLY_ACCESSIBLE_BY_USER)
            $subquery = $this->findAcosOnlyAroCanAccess($acoType, $aro, ['checkGroupsUsers' => $checkGroupsUsers]);
            // (R = R - ACOS_ONLY_ACCESSIBLE_BY_USER)
            $query->where([
                'aco' => $acoType,
                'aco_foreign_key NOT IN' => $subquery,
            ]);
        } else {
            $aros = [$aro];
            // (R = ACOS_ONLY_OWNED_BY_USER)
            $query = $this->findAcosByArosAreSoleOwner($acoType, $aros);
            // (ACOS_ONLY_ACCESSIBLE_BY_USER)
            $subquery = $this->findAcosOnlyAroCanAccess($acoType, $aro, ['checkGroupsUsers' => $checkGroupsUsers]);
            // (R = R - ACOS_ONLY_ACCESSIBLE_BY_USER)
            $query->where([
                'aco' => $acoType,
                'aco_foreign_key NOT IN' => $subquery,
            ]);
        }

        return $query;
    }

    /**
     * Returns a query retrieving the acos (resources or folders) that at least one of the given aros (users or groups)
     * is sole owner.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param array $aros An array of aro id. Composed of users or groups ids.
     * @throw \InvalidArgumentException if the aros parameter contains not only uuid value.
     * @return \Cake\ORM\Query
     */
    public function findAcosByArosAreSoleOwner(string $acoType, array $aros)
    {
        foreach ($aros as $aro) {
            if (!Validation::uuid($aro)) {
                throw new InvalidArgumentException('The access request object identifier should be a valid UUID.');
            }
        }

        // R = All the resources that are only owned by the user given in parameter or owned by non empty groups he is sole manager of.
        //
        // Details:
        // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
        // USER_AND_GROUPS, is a set of AROs represented by the user and groups given as parameter
        // OTHER_USERS_AND_GROUPS, is a set of AROs represented by the users and groups which are not USER_AND_GROUPS
        // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
        // ACOS_OWNED_BY_USERS_AND_GROUPS, is the set of AROS that are owned by the USERS_AND_GROUPS
        // ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS, is the set of AROS that are owned by the OTHER_USERS_AND_GROUPS
        // R = ACOS_OWNED_BY_USERS_AND_GROUPS - ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS, is the set of ACOS only owned by USERS_AND_GROUPS

        // (ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key NOT IN (USER_AND_GROUPS)
        // AND type = OWNER
        $acosOwnedByOtherUsersAndGroups = $this->find()
            ->select(['aco_foreign_key'])->distinct()
            ->where([
                'aco' => $acoType,
                'aro_foreign_key NOT IN' => $aros,
                'type' => Permission::OWNER,
            ]);

        // (R)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key IN (USER_AND_GROUPS)
        // AND type = OWNER
        // AND aco_foreign_key NOT IN (ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS)
        return $this->find()
            ->select(['aco_foreign_key'])->distinct()
            // ACOS_OWNED_BY_USERS_AND_GROUPS
            ->where([
                'aco' => $acoType,
                'aro_foreign_key IN' => $aros,
                'type' => Permission::OWNER,
            ])
            // ACOS_OWNED_BY_USERS_AND_GROUPS - ACOS_OWNED_BY_OTHER_USERS_AND_GROUPS
            ->where(['aco_foreign_key NOT IN' => $acosOwnedByOtherUsersAndGroups]);
    }

    /**
     * Returns a query retrieving the acos (resources or folders) a given aro (user or group) is the only one to have
     * access.
     *
     * The $checkGroupsUsers options will also check the groups the aro is member of. The returned query will return
     * also the acos that are accessible only by the groups the aro is the only member of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of.
     * ]
     * @throws \InvalidArgumentException if the aro id is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findAcosOnlyAroCanAccess(string $acoType, string $aro, ?array $options = [])
    {
        $checkGroupsUsers = Hash::get($options, 'checkGroupsUsers', false);

        // R = All the resources that are only accessible by a list of users and/or groups.
        //
        // AROS, all users or groups that have entries in the permissions table (aro_foreign_key)
        // USER_AND_GROUPS, is a set of AROs represented by the user and groups given as parameter
        // OTHER_USERS_AND_GROUPS, is a set of AROs represented by the users and groups which are not USER_AND_GROUPS
        // ACOS, all the resources that have entries in the permissions table (aro_foreign_key)
        // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS, is the set of AROS that are accessible by USERS_AND_GROUPS
        // ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS, is the set of AROS that are accessible by OTHER_USERS_AND_GROUPS
        // R = ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS - ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS, is the set of ACOS only accessible by USERS_AND_GROUPS

        // USER_AND_GROUPS
        $aros = [$aro];
        if ($checkGroupsUsers) {
            $groups = $this->Groups->GroupsUsers->findGroupsWhereUserOnlyMember($aro)
                ->all()
                ->extract('group_id')->toArray();
            $aros = array_merge($aros, $groups);
        }

        // (ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS)
        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key NOT IN (USER_AND_GROUPS)
        $acosAccessibleByOtherUsersAndGroups = $this->find()
            ->select(['aco_foreign_key'])
            ->where([
                'aco' => $acoType,
                'aro_foreign_key NOT IN' => $aros,
            ]);

        // SELECT aco_foreign_key
        // FROM permissions
        // WHERE aro_foreign_key IN (USER_AND_GROUPS)
        // AND aco_foreign_key NOT IN (ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS)
        return $this->find()
            ->select(['aco_foreign_key'])->distinct()
            // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS
            ->where([
                'aco' => $acoType,
                'aro_foreign_key IN' => $aros,
            ])
            // ACOS_ACCESSIBLE_BY_USERS_AND_GROUPS - ACOS_ACCESSIBLE_BY_OTHER_USERS_AND_GROUPS
            ->where(['aco_foreign_key NOT IN' => $acosAccessibleByOtherUsersAndGroups]);
    }

    /**
     * Find access differences between a group and a user.
     * Return only the accesses that are found in the group accesses but not in the user accesses, such as array_diff
     * will do.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $groupId The group identifier.
     * @param string $userId The user identifier.
     * @return \Cake\ORM\Query
     */
    public function findAcosAccessesDiffBetweenGroupAndUser(string $acoType, string $groupId, string $userId): Query
    {
        // R = All the resources or folders that are only accessible by a group and not accessible by a user

        // Details:
        // ACOS_GROUP_ACCESS, is the set of ACOS that a group can access
        // ACOS_USER_ACCESS, is the set of ACOS that a user can access
        // R = ACOS_GROUP_ACCESS - ACOS_USER_ACCESS

        // ACOS_USER_ACCESS
        $remainAccessAcoForeignKeysQuery = $this->findAllByAro($acoType, $userId, ['checkGroupsUsers' => true])
            ->select('aco_foreign_key');

        // R = ACOS_GROUP_ACCESS - ACOS_USER_ACCESS
        return $this->findAllByAro($acoType, $groupId)
            ->select('aco_foreign_key')
            ->where([
                'aco_foreign_key NOT IN' => $remainAccessAcoForeignKeysQuery,
            ]);
    }

    /**
     * Find access differences between a group and multiple users.
     * Return only the accesses that are found in the group accesses but not in the user accesses, such as array_diff
     * would do.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $groupId The group identifier.
     * @param string[] $usersIds The user identifier.
     * @return \Cake\ORM\Query
     */
    public function findAcosAccessesDiffBetweenGroupAndUsers(string $acoType, string $groupId, array $usersIds): Query
    {
        // @todo to document

        $directUsersAccessesQuery = $this->find()
            ->select([
                'user_id' => 'aro_foreign_key',
                'resource_id' => 'aco_foreign_key',
            ])
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::USER_ARO,
                'aro_foreign_key IN' => $usersIds,
            ]);

        $inheritedUsersAccessesExcludingGroupQuery = $this->find()
            ->select([
                'user_id' => 'groups_users.user_id',
                'resource_id' => 'aco_foreign_key',
            ])
            ->leftJoin('groups_users', 'aro_foreign_key = group_id')
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::GROUP_ARO,
                'groups_users.user_id IN' => $usersIds,
                'groups_users.group_id <>' => $groupId,
            ]);

        $groupUsersAccessesQuery = $this->find()
            ->select([
                'user_id' => 'groups_users.user_id',
                'resource_id' => 'aco_foreign_key',
            ])
            ->leftJoin('groups_users', 'aro_foreign_key = group_id')
            ->where([
                'aco' => PermissionsTable::RESOURCE_ACO,
                'aro' => PermissionsTable::GROUP_ARO,
                'aro_foreign_key' => $groupId,
            ]);

        return $groupUsersAccessesQuery
            ->leftJoin(['DirectUsersAccesses' => $directUsersAccessesQuery], [
                'DirectUsersAccesses.resource_id' => new IdentifierExpression('Permissions.aco_foreign_key'),
                'DirectUsersAccesses.user_id' => new IdentifierExpression('groups_users.user_id'),
            ])
            ->leftJoin(['InheritedUsersAccesses' => $inheritedUsersAccessesExcludingGroupQuery], [
                'InheritedUsersAccesses.resource_id' => new IdentifierExpression('Permissions.aco_foreign_key'),
                'InheritedUsersAccesses.user_id' => new IdentifierExpression('groups_users.user_id'),
            ])
            ->where(function (QueryExpression $exp) {
                return $exp->isNull('DirectUsersAccesses.resource_id')
                    ->isNull('InheritedUsersAccesses.resource_id');
            });
    }

    /**
     * Returns a query retrieving the acos (resources or folders) a given aro (user or group) is owner of.
     *
     * @param string $acoType The aco type. By instance Resource or Folder.
     * @param string $aro The target aro id. By instance a user or a group id.
     * @param array $options (optional) array of options
     * [
     *   bool $checkGroupsUsers Check also for the groups the aro is member of
     * ]
     * @throws \InvalidArgumentException if the aro foreign key is not a valid uuid
     * @return \Cake\ORM\Query
     */
    public function findAcosByAroIsOwner(string $acoType, string $aro, ?array $options = [])
    {
        return $this->findAllByAro($acoType, $aro, $options)
            ->where(['Permissions.type' => Permission::OWNER])
            ->select('aco_foreign_key')->distinct();
    }

    /**
     * Check that an aro has access to an aco.
     *
     * @param string $acoType The target aco type. By instance a Resource or a Folder.
     * @param string $acoForeignKey The target aco id. By instance a resource or a folder id.
     * @param string $aroForeignKey The target aro id. By instance a user or a group id.
     * @param int $permissionType The minimum permission type
     * @throws \InvalidArgumentException if the $aco parameter is not a valid uuid.
     * @throws \InvalidArgumentException if the $aro parameter is not a valid uuid.
     * @return bool
     */
    public function hasAccess(
        string $acoType,
        string $acoForeignKey,
        string $aroForeignKey,
        ?int $permissionType = null
    ): bool {
        if (!Validation::uuid($acoForeignKey)) {
            throw new InvalidArgumentException('The aco parameter should be a valid UUID.');
        }
        if (!Validation::uuid($aroForeignKey)) {
            throw new InvalidArgumentException('The aro parameter should be a valid UUID.');
        }
        $permissionType = $permissionType ?? Permission::READ;

        if (!$this->isValidPermissionType($permissionType)) {
            $msg = 'The permission type should be in the list of allowed permission type.';
            throw new InvalidArgumentException($msg);
        }
        $query = $this->findHighestByAcoAndAro($acoType, $acoForeignKey, $aroForeignKey)
            ->where(['Permissions.type >=' => $permissionType]);

        return $query->count() !== 0;
    }
}

Function Calls

None

Variables

None

Stats

MD5 444fddae9a219a990a4b941edb1156c6
Eval Count 0
Decode Time 90 ms