diff --git a/.eslintrc.js b/.eslintrc.js index b4cf54e72..a58941204 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,4 +6,15 @@ module.exports = { extends: [ '@nextcloud/eslint-config/typescript', ], + overrides: [ + { + files: ['src/types/openapi/*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + quotes: 'off', + 'no-multiple-empty-lines': 'off', + 'no-use-before-define': 'off', + }, + }, + ], } diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml new file mode 100644 index 000000000..cf6d2778c --- /dev/null +++ b/.github/workflows/openapi.yml @@ -0,0 +1,94 @@ +# This workflow is provided via the organization template repository +# +# https://github.com/nextcloud/.github +# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2024 Arthur Schiwon +# SPDX-License-Identifier: MIT + +name: OpenAPI + +on: pull_request + +permissions: + contents: read + +concurrency: + group: openapi-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + openapi: + runs-on: ubuntu-latest + + if: ${{ github.repository_owner != 'nextcloud-gmbh' }} + + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Get php version + id: php_versions + uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1 + + - name: Set up php + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 + with: + php-version: ${{ steps.php_versions.outputs.php-available }} + extensions: xml + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check Typescript OpenApi types + id: check_typescript_openapi + uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 + with: + files: "src/types/openapi/openapi*.ts" + + - name: Read package.json node and npm engines version + if: steps.check_typescript_openapi.outputs.files_exists == 'true' + uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 + id: node_versions + # Continue if no package.json + continue-on-error: true + with: + fallbackNode: '^20' + fallbackNpm: '^10' + + - name: Set up node ${{ steps.node_versions.outputs.nodeVersion }} + if: ${{ steps.node_versions.outputs.nodeVersion }} + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: ${{ steps.node_versions.outputs.nodeVersion }} + + - name: Set up npm ${{ steps.node_versions.outputs.npmVersion }} + if: ${{ steps.node_versions.outputs.nodeVersion }} + run: npm i -g 'npm@${{ steps.node_versions.outputs.npmVersion }}' + + - name: Install dependencies & build + if: ${{ steps.node_versions.outputs.nodeVersion }} + env: + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_DOWNLOAD: true + run: | + npm ci + + - name: Set up dependencies + run: composer i + + - name: Regenerate OpenAPI + run: composer run openapi + + - name: Check openapi*.json and typescript changes + run: | + bash -c "[[ ! \"`git status --porcelain `\" ]] || (echo 'Please run \"composer run openapi\" and commit the openapi*.json files and (if applicable) src/types/openapi/openapi*.ts, see the section \"Show changes on failure\" for details' && exit 1)" + + - name: Show changes on failure + if: failure() + run: | + git status + git --no-pager diff + exit 1 # make it red to grab attention diff --git a/README.md b/README.md index d3a9a5ce4..d8ce43826 100644 --- a/README.md +++ b/README.md @@ -126,35 +126,7 @@ To disable the advanced permissions feature for a group folder, use `occ groupfo ### REST API -Group folders can be configured externally through REST APIs. - -The following REST API's are supported: - -- `GET apps/groupfolders/folders`: Returns a list of all configured folders and their settings -- `POST apps/groupfolders/folders`: Create a new group folder - - `mountpoint`: The name for the new folder -- `GET apps/groupfolders/folders/$folderId`: Return a specific configured folder and its settings -- `DELETE apps/groupfolders/folders/$folderId`: Delete a group folder -- `POST apps/groupfolders/folders/$folderId/groups`: Give a group access to a folder - - `group`: The id of the group to be given access to the folder -- `DELETE apps/groupfolders/folders/$folderId/groups/$groupId`: Remove access from a group to a folder -- `POST apps/groupfolders/folders/$folderId/acl`: Enable/Disable folder advanced permissions - - `acl` 1 for enable, 0 for disable. -- `POST apps/groupfolders/folders/$folderId/manageACL`: Grants/Removes a group or user the ability to manage a groupfolders' advanced permissions - - `$mappingId`: the id of the group/user to be granted/removed access to/from the folder - - `$mappingType`: 'group' or 'user' - - `$manageAcl`: true to grants ability to manage a groupfolders' advanced permissions, false to remove -- `POST apps/groupfolders/folders/$folderId/groups/$groupId`: Set the permissions a group has in a folder - - `permissions` The new permissions for the group as bitmask of [permissions constants](https://github.com/nextcloud/server/blob/b4f36d44c43aac0efdc6c70ff8e46473341a9bfe/lib/public/Constants.php#L65) -- `POST apps/groupfolders/folders/$folderId/quota`: Set the quota for a folder - - `quota`: The new quota for the folder in bytes, user `-3` for unlimited -- `POST apps/groupfolders/folders/$folderId/mountpoint`: Change the name of a folder - - `mountpoint`: The new name for the folder - -For all `POST` calls the required parameters are listed. - -Non-admins can access the `GET` requests to retrieve info about group folders they have access to. -Admins can add `applicable=1` as a parameter to the group folder list request to get the same filtered results of only folders they have direct access to. +See the [OpenAPI specification](openapi.json) to learn about all available API endpoints. ### WebDAV API diff --git a/REUSE.toml b/REUSE.toml index 3ad49a1f1..91ec72aad 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -76,3 +76,9 @@ path = ["tests/stubs/icewind_streams_**"] precedence = "aggregate" SPDX-FileCopyrightText = "none" SPDX-License-Identifier = "MIT" + +[[annotations]] +path = ["openapi.json", "src/types/openapi/openapi.ts"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors" +SPDX-License-Identifier = "AGPL-3.0-or-later" diff --git a/composer.json b/composer.json index e790aa28f..85d9e99a8 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "psalm:fix": "psalm --no-cache --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", "test:unit": "phpunit -c tests/phpunit.xml", "test:unit:coverage": "XDEBUG_MODE=coverage phpunit -c tests/phpunit.xml", - "rector": "rector && composer cs:fix" + "rector": "rector && composer cs:fix", + "openapi": "generate-spec && npm run typescript:generate" }, "config": { "allow-plugins": { diff --git a/lib/AppInfo/Capabilities.php b/lib/AppInfo/Capabilities.php index bed591df2..32a2dbb14 100644 --- a/lib/AppInfo/Capabilities.php +++ b/lib/AppInfo/Capabilities.php @@ -21,6 +21,14 @@ public function __construct( ) { } + /** + * @return array{ + * groupfolders?: array{ + * appVersion: string, + * hasGroupFolders: bool, + * }, + * } + */ public function getCapabilities(): array { $user = $this->userSession->getUser(); if (!$user) { diff --git a/lib/Command/FolderCommand.php b/lib/Command/FolderCommand.php index 05cde733d..8ef504380 100644 --- a/lib/Command/FolderCommand.php +++ b/lib/Command/FolderCommand.php @@ -11,12 +11,15 @@ use OC\Core\Command\Base; use OCA\GroupFolders\Folder\FolderManager; use OCA\GroupFolders\Mount\MountProvider; +use OCA\GroupFolders\ResponseDefinitions; use OCP\Files\IRootFolder; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Base command for commands asking the user for a folder id. + * + * @psalm-import-type GroupFoldersFolder from ResponseDefinitions */ abstract class FolderCommand extends Base { @@ -29,7 +32,7 @@ public function __construct( } /** - * @psalm-return ?array{id: mixed, mount_point: string, groups: array|mixed, quota: int, size: int|mixed, acl: bool} + * @return ?GroupFoldersFolder */ protected function getFolder(InputInterface $input, OutputInterface $output): ?array { $folderId = (int)$input->getArgument('folder_id'); diff --git a/lib/Command/Group.php b/lib/Command/Group.php index f82d61250..888488e8c 100644 --- a/lib/Command/Group.php +++ b/lib/Command/Group.php @@ -62,7 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $permissionsString = $input->getArgument('permissions'); $permissions = $this->getNewPermissions($permissionsString); if ($permissions) { - if (!isset($folder['groups'][$groupString])) { + if (!is_array($folder['groups'])) { + $folder['groups'] = []; + } + if (empty(array_filter($folder['groups'], static fn (array $group) => $group['id'] === $groupString))) { $this->folderManager->addApplicableGroup($folder['id'], $groupString); } diff --git a/lib/Command/ListCommand.php b/lib/Command/ListCommand.php index e1473e21b..b8228a60b 100644 --- a/lib/Command/ListCommand.php +++ b/lib/Command/ListCommand.php @@ -87,8 +87,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { foreach ($folders as &$folder) { - $folder['group_details'] = $folder['groups']; - $folder['groups'] = array_map(fn (array $group): int => $group['permissions'], $folder['groups']); + if (is_array($folder['groups'])) { + $folder['groups'] = array_map(fn (array $group): int => $group['permissions'], $folder['groups']); + } else { + $folder['groups'] = []; + } } $this->writeArrayInOutputFormat($input, $output, $folders); @@ -98,13 +101,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $table->setRows(array_map(function (array $folder) use ($groupNames): array { $folder['size'] = \OCP\Util::humanFileSize($folder['size']); $folder['quota'] = ($folder['quota'] > 0) ? \OCP\Util::humanFileSize($folder['quota']) : 'Unlimited'; - $groupStrings = array_map(function (string $groupId, array $entry) use ($groupNames): string { - [$permissions, $displayName] = [$entry['permissions'], $entry['displayName']]; - $groupName = array_key_exists($groupId, $groupNames) && ($groupNames[$groupId] !== $groupId) ? $groupNames[$groupId] . ' (' . $groupId . ')' : $displayName; - - return $groupName . ': ' . $this->permissionsToString($permissions); - }, array_keys($folder['groups']), array_values($folder['groups'])); - $folder['groups'] = implode("\n", $groupStrings); + if (is_array($folder['groups'])) { + $groupStrings = array_map(function (string $groupId, array $entry) use ($groupNames): string { + [$permissions, $displayName] = [$entry['permissions'], $entry['displayName']]; + $groupName = array_key_exists($groupId, $groupNames) && ($groupNames[$groupId] !== $groupId) ? $groupNames[$groupId] . ' (' . $groupId . ')' : $displayName; + + return $groupName . ': ' . $this->permissionsToString($permissions); + }, array_keys($folder['groups']), array_values($folder['groups'])); + $folder['groups'] = implode("\n", $groupStrings); + } else { + $folder['groups'] = []; + } $folder['acl'] = $folder['acl'] ? 'Enabled' : 'Disabled'; $manageStrings = array_map(fn (array $manage): string => $manage['id'] . ' (' . $manage['type'] . ')', $folder['manage']); $folder['manage'] = implode("\n", $manageStrings); diff --git a/lib/Command/Scan.php b/lib/Command/Scan.php index 5c0703a8e..3609990b8 100644 --- a/lib/Command/Scan.php +++ b/lib/Command/Scan.php @@ -67,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return -1; } - $folders = [$folder['id'] => $folder]; + $folders = [$folder]; } $inputPath = $input->getOption('path'); diff --git a/lib/Controller/DelegationController.php b/lib/Controller/DelegationController.php index ead39573e..ba97a87d4 100644 --- a/lib/Controller/DelegationController.php +++ b/lib/Controller/DelegationController.php @@ -9,9 +9,11 @@ use OCA\Circles\CirclesManager; use OCA\GroupFolders\Attribute\RequireGroupFolderAdmin; +use OCA\GroupFolders\ResponseDefinitions; use OCA\GroupFolders\Service\DelegationService; use OCA\Settings\Service\AuthorizedGroupService; use OCP\App\IAppManager; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; @@ -23,6 +25,10 @@ use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +/** + * @psalm-import-type GroupFoldersGroup from ResponseDefinitions + * @psalm-import-type GroupFoldersCircle from ResponseDefinitions + */ class DelegationController extends OCSController { public function __construct( string $appName, @@ -39,6 +45,10 @@ public function __construct( /** * Returns the list of all groups + * + * @return DataResponse, array{}> + * + * 200: All groups returned */ #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -61,6 +71,10 @@ public function getAllGroups(): DataResponse { /** * Returns the list of all visible circles + * + * @return DataResponse, array{}> + * + * 200: All circles returned */ #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -97,10 +111,14 @@ public function getAllCircles(): DataResponse { /** * Get the list Groups related to classname. - * If the classname is + * @param string $classname If the classname is * - OCA\GroupFolders\Settings\Admin : It's reference to fields in Admin Priveleges. * - OCA\GroupFolders\Controller\DelegationController : It's just to specific the subadmins. * They can only manage groupfolders in which they are added in the Advanced Permissions (groups only) + * + * @return DataResponse, array{}> + * + * 200: Authorized groups returned */ #[RequireGroupFolderAdmin] #[NoAdminRequired] diff --git a/lib/Controller/FolderController.php b/lib/Controller/FolderController.php index b9f598324..4551e8572 100644 --- a/lib/Controller/FolderController.php +++ b/lib/Controller/FolderController.php @@ -7,10 +7,10 @@ namespace OCA\GroupFolders\Controller; -use OC\AppFramework\OCS\V1Response; use OCA\GroupFolders\Attribute\RequireGroupFolderAdmin; use OCA\GroupFolders\Folder\FolderManager; use OCA\GroupFolders\Mount\MountProvider; +use OCA\GroupFolders\ResponseDefinitions; use OCA\GroupFolders\Service\DelegationService; use OCA\GroupFolders\Service\FoldersFilter; use OCP\AppFramework\Http; @@ -27,6 +27,11 @@ use OCP\IUser; use OCP\IUserSession; +/** + * @psalm-import-type GroupFoldersGroup from ResponseDefinitions + * @psalm-import-type GroupFoldersUser from ResponseDefinitions + * @psalm-import-type GroupFoldersFolder from ResponseDefinitions + */ class FolderController extends OCSController { private ?IUser $user; @@ -43,12 +48,13 @@ public function __construct( ) { parent::__construct($AppName, $request); $this->user = $userSession->getUser(); - - $this->registerResponder('xml', fn (DataResponse $data): V1Response => $this->buildOCSResponseXML('xml', $data)); } /** * Regular users can access their own folders, but they only get to see the permission for their own groups + * + * @param GroupFoldersFolder $folder + * @return GroupFoldersFolder|null */ private function filterNonAdminFolder(array $folder): ?array { if ($this->user === null) { @@ -56,36 +62,34 @@ private function filterNonAdminFolder(array $folder): ?array { } $userGroups = $this->groupManager->getUserGroupIds($this->user); - $folder['groups'] = array_filter($folder['groups'], fn (string $group): bool => in_array($group, $userGroups), ARRAY_FILTER_USE_KEY); - if ($folder['groups']) { - return $folder; - } else { - return null; + if (is_array($folder['groups'])) { + $folder['groups'] = array_filter($folder['groups'], fn (string $group): bool => in_array($group, $userGroups), ARRAY_FILTER_USE_KEY); + if (!empty($folder['groups'])) { + return $folder; + } } + + return null; } /** - * @param array{acl: bool, groups: array, id: int, manage: array, mount_point: mixed, quota: int, size: int} $folder - * @return array{acl: bool, group_details: array, groups: array, id: int, manage: array, mount_point: mixed, quota: int, size: int} + * Gets all Groupfolders + * + * @param bool $applicable Filter by applicable groups + * @return DataResponse, array{}>|DataResponse, array{}> + * + * 200: Groupfolders returned + * 404: Storage not found */ - private function formatFolder(array $folder): array { - // keep compatibility with the old 'groups' field - $folder['group_details'] = $folder['groups']; - $folder['groups'] = array_map(fn (array $group): int => $group['permissions'], $folder['groups']); - - return $folder; - } - #[NoAdminRequired] #[ApiRoute(verb: 'GET', url: '/folders')] public function getFolders(bool $applicable = false): DataResponse { $storageId = $this->getRootFolderStorageId(); if ($storageId === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } $folders = $this->manager->getAllFoldersWithSize($storageId); - $folders = array_map($this->formatFolder(...), $folders); $isAdmin = $this->delegationService->isAdminNextcloud() || $this->delegationService->isDelegatedAdmin(); if ($isAdmin && !$applicable) { return new DataResponse($folders); @@ -96,13 +100,21 @@ public function getFolders(bool $applicable = false): DataResponse { } if ($applicable || !$this->delegationService->hasApiAccess()) { - $folders = array_map($this->filterNonAdminFolder(...), $folders); - $folders = array_filter($folders); + $folders = array_values(array_filter(array_map($this->filterNonAdminFolder(...), $folders))); } return new DataResponse($folders); } + /** + * Gets a Groupfolder by ID + * + * @param int $id ID of the Groupfolder + * @return DataResponse|DataResponse, array{}> + * + * 200: Groupfolder returned + * 404: Groupfolder not found + */ #[NoAdminRequired] #[ApiRoute(verb: 'GET', url: '/folders/{id}')] public function getFolder(int $id): DataResponse { @@ -113,24 +125,27 @@ public function getFolder(int $id): DataResponse { $storageId = $this->getRootFolderStorageId(); if ($storageId === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } $folder = $this->manager->getFolder($id, $storageId); if ($folder === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } if (!$this->delegationService->hasApiAccess()) { $folder = $this->filterNonAdminFolder($folder); if ($folder === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } } - return new DataResponse($this->formatFolder($folder)); + return new DataResponse($folder); } + /** + * @return DataResponse, array{}>|null + */ private function checkFolderExists(int $id): ?DataResponse { $storageId = $this->getRootFolderStorageId(); if ($storageId === null) { @@ -150,7 +165,13 @@ private function getRootFolderStorageId(): ?int { } /** - * @throws OCSNotFoundException + * Add a new Groupfolder + * + * @param string $mountpoint Mountpoint of the new Groupfolder + * @return DataResponse|DataResponse, array{}> + * + * 200: Groupfolder added successfully + * 404: Groupfolder not found */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] @@ -160,18 +181,27 @@ public function addFolder(string $mountpoint): DataResponse { $storageId = $this->rootFolder->getMountPoint()->getNumericStorageId(); if ($storageId === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } $id = $this->manager->createFolder(trim($mountpoint)); $folder = $this->manager->getFolder($id, $storageId); if ($folder === null) { - throw new OCSNotFoundException(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } return new DataResponse($folder); } + /** + * Remove a Groupfolder + * + * @param int $id ID of the Groupfolder + * @return DataResponse|DataResponse, array{}> + * + * 200: Groupfolder removed successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -193,15 +223,16 @@ public function removeFolder(int $id): DataResponse { return new DataResponse(['success' => true]); } - #[PasswordConfirmationRequired] - #[RequireGroupFolderAdmin] - #[NoAdminRequired] - #[ApiRoute(verb: 'PUT', url: '/folders/{id}')] - public function setMountPoint(int $id, string $mountPoint): DataResponse { - $this->manager->renameFolder($id, trim($mountPoint)); - return new DataResponse(['success' => true]); - } - + /** + * Add access of a group for a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param string $group Group to add access for + * @return DataResponse|DataResponse, array{}> + * + * 200: Group access added sucessfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -217,6 +248,16 @@ public function addGroup(int $id, string $group): DataResponse { return new DataResponse(['success' => true]); } + /** + * Remove access of a group from a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param string $group Group to remove access from + * @return DataResponse|DataResponse, array{}> + * + * 200: Group access removed successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -232,6 +273,17 @@ public function removeGroup(int $id, string $group): DataResponse { return new DataResponse(['success' => true]); } + /** + * Set the permissions of a group for a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param string $group Group for which the permissions will be set + * @param int $permissions New permissions + * @return DataResponse|DataResponse, array{}> + * + * 200: Permissions updated successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -248,7 +300,17 @@ public function setPermissions(int $id, string $group, int $permissions): DataRe } /** + * Updates an ACL mapping + * + * @param int $id ID of the Groupfolder + * @param string $mappingType Type of the ACL mapping + * @param string $mappingId ID of the ACL mapping + * @param bool $manageAcl Whether to enable or disable the ACL mapping + * @return DataResponse|DataResponse, array{}> * @throws \OCP\DB\Exception + * + * 200: ACL mapping updated successfully + * 404: Groupfolder not found */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] @@ -265,6 +327,16 @@ public function setManageACL(int $id, string $mappingType, string $mappingId, bo return new DataResponse(['success' => true]); } + /** + * Set a new quota for a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param int $quota New quota in bytes + * @return DataResponse|DataResponse, array{}> + * + * 200: New quota set successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -280,6 +352,16 @@ public function setQuota(int $id, int $quota): DataResponse { return new DataResponse(['success' => true]); } + /** + * Toggle the ACL for a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param bool $acl Whether ACL should be enabled or not + * @return DataResponse|DataResponse, array{}> + * + * 200: ACL toggled successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -295,6 +377,16 @@ public function setACL(int $id, bool $acl): DataResponse { return new DataResponse(['success' => true]); } + /** + * Rename a Groupfolder + * + * @param int $id ID of the Groupfolder + * @param string $mountpoint New Mountpoint of the Groupfolder + * @return DataResponse|DataResponse, array{}> + * + * 200: Groupfolder renamed successfully + * 404: Groupfolder not found + */ #[PasswordConfirmationRequired] #[RequireGroupFolderAdmin] #[NoAdminRequired] @@ -311,43 +403,17 @@ public function renameFolder(int $id, string $mountpoint): DataResponse { } /** - * Overwrite response builder to customize xml handling to deal with spaces in folder names + * Searches for matching ACL mappings + * + * @param int $id The ID of the Groupfolder + * @param string $search String to search by + * @return DataResponse, groups: list}, array{}> + * + * 200: ACL Mappings returned */ - private function buildOCSResponseXML(string $format, DataResponse $data): V1Response { - /** @var array $folderData */ - $folderData = $data->getData(); - if (isset($folderData['id'])) { - // single folder response - $folderData = $this->folderDataForXML($folderData); - } elseif (is_array($folderData) && count($folderData) && isset(current($folderData)['id'])) { - // folder list - $folderData = array_map($this->folderDataForXML(...), $folderData); - } - - $data->setData($folderData); - - return new V1Response($data, $format); - } - - private function folderDataForXML(array $data): array { - $groups = $data['group_details'] ?? []; - unset($data['group_details']); - $data['groups'] = []; - foreach ($groups as $id => $group) { - $data['groups'][] = [ - '@group_id' => $id, - '@permissions' => $group['permissions'], - '@display-name' => $group['displayName'], - '@type' => $group['type'], - ]; - } - - return $data; - } - #[NoAdminRequired] #[ApiRoute(verb: 'GET', url: '/folders/{id}/search')] - public function aclMappingSearch(int $id, ?int $fileId, string $search = ''): DataResponse { + public function aclMappingSearch(int $id, string $search = ''): DataResponse { $users = []; $groups = []; diff --git a/lib/DAV/GroupFoldersHome.php b/lib/DAV/GroupFoldersHome.php index d7c894d8a..9d949eff9 100644 --- a/lib/DAV/GroupFoldersHome.php +++ b/lib/DAV/GroupFoldersHome.php @@ -9,8 +9,8 @@ namespace OCA\GroupFolders\DAV; use OC\Files\Filesystem; +use OCA\GroupFolders\Folder\Folder; use OCA\GroupFolders\Folder\FolderManager; -use OCP\Files\Cache\ICacheEntry; use OCP\Files\IRootFolder; use OCP\IUser; use RuntimeException; @@ -48,10 +48,7 @@ public function createDirectory($name): never { throw new Forbidden('Permission denied to create folders in this folder'); } - /** - * @return array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?ICacheEntry}|null - */ - private function getFolder(string $name): ?array { + private function getFolder(string $name): ?Folder { $storageId = $this->rootFolder->getMountPoint()->getNumericStorageId(); if ($storageId === null) { return null; @@ -59,7 +56,7 @@ private function getFolder(string $name): ?array { $folders = $this->folderManager->getFoldersForUser($this->user, $storageId); foreach ($folders as $folder) { - if (basename($folder['mount_point']) === $name) { + if (basename($folder->mountPoint) === $name) { return $folder; } } @@ -67,19 +64,16 @@ private function getFolder(string $name): ?array { return null; } - /** - * @param array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?ICacheEntry} $folder - */ - private function getDirectoryForFolder(array $folder): GroupFolderNode { + private function getDirectoryForFolder(Folder $folder): GroupFolderNode { $userHome = '/' . $this->user->getUID() . '/files'; - $node = $this->rootFolder->get($userHome . '/' . $folder['mount_point']); + $node = $this->rootFolder->get($userHome . '/' . $folder->mountPoint); $view = Filesystem::getView(); if ($view === null) { throw new RuntimeException('Unable to create view.'); } - return new GroupFolderNode($view, $node, $folder['folder_id']); + return new GroupFolderNode($view, $node, $folder->folderId); } public function getChild($name): GroupFolderNode { diff --git a/lib/Folder/Folder.php b/lib/Folder/Folder.php new file mode 100644 index 000000000..612a3dd67 --- /dev/null +++ b/lib/Folder/Folder.php @@ -0,0 +1,24 @@ +>, id: int, mount_point: mixed, quota: int, size: 0}> + * @return list * @throws Exception */ public function getAllFolders(): array { @@ -59,10 +69,14 @@ public function getAllFolders(): array { $folderMap = []; foreach ($rows as $row) { $id = (int)$row['folder_id']; - $folderMap[$id] = [ + $applicables = $applicableMap[$id] ?? []; + if (empty($applicables)) { + $applicables = new stdClass(); + } + $folderMap[] = [ 'id' => $id, 'mount_point' => $row['mount_point'], - 'groups' => $applicableMap[$id] ?? [], + 'groups' => $applicables, 'quota' => (int)$row['quota'], 'size' => 0, 'acl' => (bool)$row['acl'] @@ -95,7 +109,7 @@ private function joinQueryWithFileCache(IQueryBuilder $query, int $rootStorageId } /** - * @return array, id: int, manage: array, mount_point: mixed, quota: int, size: int}> + * @return list * @throws Exception */ public function getAllFoldersWithSize(int $rootStorageId): array { @@ -114,11 +128,15 @@ public function getAllFoldersWithSize(int $rootStorageId): array { $folderMap = []; foreach ($rows as $row) { $id = (int)$row['folder_id']; + $applicables = $applicableMap[$id] ?? []; + if (empty($applicables)) { + $applicables = new stdClass(); + } $mappings = $folderMappings[$id] ?? []; - $folderMap[$id] = [ + $folderMap[] = [ 'id' => $id, - 'mount_point' => $row['mount_point'], - 'groups' => $applicableMap[$id] ?? [], + 'mount_point' => (string)$row['mount_point'], + 'groups' => $applicables, 'quota' => (int)$row['quota'], 'size' => $row['size'] ? (int)$row['size'] : 0, 'acl' => (bool)$row['acl'], @@ -130,7 +148,7 @@ public function getAllFoldersWithSize(int $rootStorageId): array { } /** - * @return array, id: int, manage: array, mount_point: mixed, quota: int, size: int}> + * @return list * @throws Exception */ public function getAllFoldersForUserWithSize(int $rootStorageId, IUser $user): array { @@ -157,11 +175,15 @@ public function getAllFoldersForUserWithSize(int $rootStorageId, IUser $user): a $folderMap = []; foreach ($rows as $row) { $id = (int)$row['folder_id']; + $applicables = $applicableMap[$id] ?? []; + if (empty($applicables)) { + $applicables = new stdClass(); + } $mappings = $folderMappings[$id] ?? []; - $folderMap[$id] = [ + $folderMap[] = [ 'id' => $id, 'mount_point' => $row['mount_point'], - 'groups' => $applicableMap[$id] ?? [], + 'groups' => $applicables, 'quota' => (int)$row['quota'], 'size' => $row['size'] ? (int)$row['size'] : 0, 'acl' => (bool)$row['acl'], @@ -173,7 +195,7 @@ public function getAllFoldersForUserWithSize(int $rootStorageId, IUser $user): a } /** - * @return array> + * @return array> * @throws Exception */ private function getAllFolderMappings(): array { @@ -188,18 +210,19 @@ private function getAllFolderMappings(): array { foreach ($rows as $row) { $id = (int)$row['folder_id']; - if (!isset($folderMap[$id])) { - $folderMap[$id] = [$row]; - } else { - $folderMap[$id][] = $row; - } + $folderMap[$id] ??= []; + $folderMap[$id][] = new FolderMapping( + (int)$row['folder_id'], + (string)$row['mapping_type'], + (string)$row['mapping_id'], + ); } return $folderMap; } /** - * @return array> + * @return list * @throws Exception */ private function getFolderMappings(int $id): array { @@ -208,16 +231,21 @@ private function getFolderMappings(int $id): array { ->from('group_folders_manage') ->where($query->expr()->eq('folder_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); - return $query->executeQuery()->fetchAll(); + return array_values(array_map(static fn (array $row): FolderMapping => new FolderMapping( + (int)$row['folder_id'], + (string)$row['mapping_type'], + (string)$row['mapping_id'], + ), $query->executeQuery()->fetchAll())); } /** - * @return array{type: 'user'|'group', id: string, displayname: string}[] + * @param list $mappings + * @return list */ private function getManageAcl(array $mappings): array { - return array_filter(array_map(function (array $entry): ?array { - if ($entry['mapping_type'] === 'user') { - $user = Server::get(IUserManager::class)->get($entry['mapping_id']); + return array_values(array_filter(array_map(function (FolderMapping $entry): ?array { + if ($entry->mappingType === 'user') { + $user = Server::get(IUserManager::class)->get($entry->mappingId); if ($user === null) { return null; } @@ -225,11 +253,11 @@ private function getManageAcl(array $mappings): array { return [ 'type' => 'user', 'id' => (string)$user->getUID(), - 'displayname' => (string)$user->getDisplayName() + 'displayName' => (string)$user->getDisplayName() ]; } - $group = Server::get(IGroupManager::class)->get($entry['mapping_id']); + $group = Server::get(IGroupManager::class)->get($entry->mappingId); if ($group === null) { return null; } @@ -237,13 +265,13 @@ private function getManageAcl(array $mappings): array { return [ 'type' => 'group', 'id' => $group->getGID(), - 'displayname' => $group->getDisplayName() + 'displayName' => $group->getDisplayName() ]; - }, $mappings)); + }, $mappings))); } /** - * @return ?array{id: mixed, mount_point: mixed, groups: array, quota: int, size: int, acl: bool} + * @return ?GroupFoldersFolder * @throws Exception */ public function getFolder(int $id, int $rootStorageId): ?array { @@ -263,12 +291,17 @@ public function getFolder(int $id, int $rootStorageId): ?array { return null; } + $applicables = $applicableMap[$id] ?? []; + if (empty($applicables)) { + $applicables = new stdClass(); + } + $folderMappings = $this->getFolderMappings($id); return [ 'id' => $id, 'mount_point' => (string)$row['mount_point'], - 'groups' => $applicableMap[$id] ?? [], + 'groups' => $applicables, 'quota' => (int)$row['quota'], 'size' => $row['size'] ?: 0, 'acl' => (bool)$row['acl'], @@ -303,7 +336,7 @@ public function getFolderByPath(string $path): int { } /** - * @return array>> + * @return array> * @throws Exception */ private function getAllApplicable(): array { @@ -324,15 +357,16 @@ private function getAllApplicable(): array { } if (!$row['circle_id']) { - $entityId = $row['group_id']; + $entityId = (string)$row['group_id']; $entry = [ - 'displayName' => $row['group_id'], + 'displayName' => $entityId, 'permissions' => (int)$row['permissions'], 'type' => 'group' ]; } else { - $entityId = $row['circle_id']; + $entityId = (string)$row['circle_id']; + try { $circle = $queryHelper?->extractCircle($row); } catch (CircleNotFoundException) { @@ -340,7 +374,7 @@ private function getAllApplicable(): array { } $entry = [ - 'displayName' => $circle?->getDisplayName() ?? $row['circle_id'], + 'displayName' => $circle?->getDisplayName() ?? $entityId, 'permissions' => (int)$row['permissions'], 'type' => 'circle' ]; @@ -354,15 +388,16 @@ private function getAllApplicable(): array { /** * @throws Exception + * @return list */ private function getGroups(int $id): array { $groups = $this->getAllApplicable()[$id] ?? []; $groups = array_map(fn (string $gid): ?IGroup => $this->groupManager->get($gid), array_keys($groups)); - return array_map(fn (IGroup $group): array => [ + return array_values(array_map(fn (IGroup $group): array => [ 'gid' => $group->getGID(), - 'displayname' => $group->getDisplayName() - ], array_filter($groups)); + 'displayName' => $group->getDisplayName() + ], array_filter($groups))); } /** @@ -378,10 +413,10 @@ public function canManageACL(int $folderId, IUser $user): bool { } // Call private server api - if (class_exists(\OC\Settings\AuthorizedGroupMapper::class)) { - $authorizedGroupMapper = Server::get(\OC\Settings\AuthorizedGroupMapper::class); + if (class_exists(AuthorizedGroupMapper::class)) { + $authorizedGroupMapper = Server::get(AuthorizedGroupMapper::class); $settingClasses = $authorizedGroupMapper->findAllClassesForUser($user); - if (in_array(\OCA\GroupFolders\Settings\Admin::class, $settingClasses, true)) { + if (in_array(Admin::class, $settingClasses, true)) { return true; } } @@ -413,6 +448,7 @@ public function canManageACL(int $folderId, IUser $user): bool { /** * @throws Exception + * @return list */ public function searchGroups(int $id, string $search = ''): array { $groups = $this->getGroups($id); @@ -420,11 +456,12 @@ public function searchGroups(int $id, string $search = ''): array { return $groups; } - return array_filter($groups, fn (array $group): bool => (stripos($group['gid'], $search) !== false) || (stripos($group['displayname'], $search) !== false)); + return array_values(array_filter($groups, fn (array $group): bool => (stripos($group['gid'], $search) !== false) || (stripos($group['displayName'], $search) !== false))); } /** * @throws Exception + * @return list */ public function searchUsers(int $id, string $search = '', int $limit = 10, int $offset = 0): array { $groups = $this->getGroups($id); @@ -437,7 +474,7 @@ public function searchUsers(int $id, string $search = '', int $limit = 10, int $ if (!isset($users[$uid])) { $users[$uid] = [ 'uid' => $uid, - 'displayname' => $displayName + 'displayName' => $displayName ]; } } @@ -448,7 +485,7 @@ public function searchUsers(int $id, string $search = '', int $limit = 10, int $ } /** - * @return list + * @return list * @throws Exception */ public function getFoldersForGroup(string $groupId, int $rootStorageId = 0): array { @@ -486,19 +523,19 @@ public function getFoldersForGroup(string $groupId, int $rootStorageId = 0): arr $result = $query->executeQuery()->fetchAll(); - return array_values(array_map(fn (array $folder): array => [ - 'folder_id' => (int)$folder['folder_id'], - 'mount_point' => (string)$folder['mount_point'], - 'permissions' => (int)$folder['group_permissions'], - 'quota' => (int)$folder['quota'], - 'acl' => (bool)$folder['acl'], - 'rootCacheEntry' => (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null - ], $result)); + return array_values(array_map(fn (array $folder): Folder => new Folder( + (int)$folder['folder_id'], + (string)$folder['mount_point'], + (int)$folder['group_permissions'], + (int)$folder['quota'], + (bool)$folder['acl'], + (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null + ), $result)); } /** * @param string[] $groupIds - * @return array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?CacheEntry}[] + * @return list * @throws Exception */ public function getFoldersForGroups(array $groupIds, int $rootStorageId = 0): array { @@ -541,18 +578,18 @@ public function getFoldersForGroups(array $groupIds, int $rootStorageId = 0): ar $result = array_merge($result, $query->executeQuery()->fetchAll()); } - return array_map(fn (array $folder): array => [ - 'folder_id' => (int)$folder['folder_id'], - 'mount_point' => (string)$folder['mount_point'], - 'permissions' => (int)$folder['group_permissions'], - 'quota' => (int)$folder['quota'], - 'acl' => (bool)$folder['acl'], - 'rootCacheEntry' => (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null - ], $result); + return array_values(array_map(fn (array $folder): Folder => new Folder( + (int)$folder['folder_id'], + (string)$folder['mount_point'], + (int)$folder['group_permissions'], + (int)$folder['quota'], + (bool)$folder['acl'], + (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null + ), $result)); } /** - * @return array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?CacheEntry}[] + * @return list * @throws Exception */ public function getFoldersFromCircleMemberships(IUser $user, int $rootStorageId = 0): array { @@ -601,14 +638,14 @@ public function getFoldersFromCircleMemberships(IUser $user, int $rootStorageId $queryHelper->limitToInheritedMembers('a', 'circle_id', $federatedUser); $this->joinQueryWithFileCache($query, $rootStorageId); - return array_map(fn (array $folder): array => [ - 'folder_id' => (int)$folder['folder_id'], - 'mount_point' => (string)$folder['mount_point'], - 'permissions' => (int)$folder['group_permissions'], - 'quota' => (int)$folder['quota'], - 'acl' => (bool)$folder['acl'], - 'rootCacheEntry' => (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null - ], $query->executeQuery()->fetchAll()); + return array_values(array_map(fn (array $folder): Folder => new Folder( + (int)$folder['folder_id'], + (string)$folder['mount_point'], + (int)$folder['group_permissions'], + (int)$folder['quota'], + (bool)$folder['acl'], + (isset($folder['fileid'])) ? Cache::cacheEntryFromData($folder, $this->mimeTypeLoader) : null + ), $query->executeQuery()->fetchAll())); } @@ -835,7 +872,7 @@ public function setFolderACL(int $folderId, bool $acl): void { } /** - * @return list + * @return list * @throws Exception */ public function getFoldersForUser(IUser $user, int $rootStorageId = 0): array { @@ -847,9 +884,9 @@ public function getFoldersForUser(IUser $user, int $rootStorageId = 0): array { $mergedFolders = []; foreach ($folders as $folder) { - $id = $folder['folder_id']; + $id = $folder->folderId; if (isset($mergedFolders[$id])) { - $mergedFolders[$id]['permissions'] |= $folder['permissions']; + $mergedFolders[$id]->permissions |= $folder->permissions; } else { $mergedFolders[$id] = $folder; } @@ -870,8 +907,8 @@ public function getFolderPermissionsForUser(IUser $user, int $folderId): int { $permissions = 0; foreach ($folders as $folder) { - if ($folderId === $folder['folder_id']) { - $permissions |= $folder['permissions']; + if ($folderId === $folder->folderId) { + $permissions |= $folder->permissions; } } diff --git a/lib/Folder/FolderMapping.php b/lib/Folder/FolderMapping.php new file mode 100644 index 000000000..212ab82ad --- /dev/null +++ b/lib/Folder/FolderMapping.php @@ -0,0 +1,19 @@ +manager->getAllFolders() as $id => $folder) { + foreach ($this->manager->getAllFolders() as $folder) { $quota = $folder['quota']; $changed = false; @@ -42,7 +42,7 @@ public function run(IOutput $output): void { } if ($changed) { - $this->manager->setFolderQuota($id, $quota); + $this->manager->setFolderQuota($folder['id'], $quota); } } } diff --git a/lib/Mount/MountProvider.php b/lib/Mount/MountProvider.php index 5d966caa9..6152b24a1 100644 --- a/lib/Mount/MountProvider.php +++ b/lib/Mount/MountProvider.php @@ -6,7 +6,6 @@ namespace OCA\GroupFolders\Mount; -use OC\Files\Cache\CacheEntry; use OC\Files\Storage\Wrapper\Jail; use OC\Files\Storage\Wrapper\PermissionsMask; use OCA\GroupFolders\ACL\ACLManager; @@ -64,7 +63,7 @@ private function getRootStorageId(): int { } /** - * @return list + * @return list<\OCA\GroupFolders\Folder\Folder> */ public function getFoldersForUser(IUser $user): array { return $this->folderManager->getFoldersForUser($user, $this->getRootStorageId()); @@ -73,23 +72,23 @@ public function getFoldersForUser(IUser $user): array { public function getMountsForUser(IUser $user, IStorageFactory $loader): array { $folders = $this->getFoldersForUser($user); - $mountPoints = array_map(fn (array $folder): string => 'files/' . $folder['mount_point'], $folders); + $mountPoints = array_map(fn (\OCA\GroupFolders\Folder\Folder $folder): string => 'files/' . $folder->mountPoint, $folders); $conflicts = $this->findConflictsForUser($user, $mountPoints); - $foldersWithAcl = array_filter($folders, fn (array $folder): bool => $folder['acl']); - $aclRootPaths = array_map(fn (array $folder): string => $this->getJailPath($folder['folder_id']), $foldersWithAcl); + $foldersWithAcl = array_filter($folders, fn (\OCA\GroupFolders\Folder\Folder $folder): bool => $folder->acl); + $aclRootPaths = array_map(fn (\OCA\GroupFolders\Folder\Folder $folder): string => $this->getJailPath($folder->folderId), $foldersWithAcl); $aclManager = $this->aclManagerFactory->getACLManager($user, $this->getRootStorageId()); $rootRules = $aclManager->getRelevantRulesForPath($aclRootPaths); - return array_values(array_filter(array_map(function (array $folder) use ($user, $loader, $conflicts, $aclManager, $rootRules): ?IMountPoint { + return array_values(array_filter(array_map(function (\OCA\GroupFolders\Folder\Folder $folder) use ($user, $loader, $conflicts, $aclManager, $rootRules): ?IMountPoint { // check for existing files in the user home and rename them if needed - $originalFolderName = $folder['mount_point']; + $originalFolderName = $folder->mountPoint; if (in_array($originalFolderName, $conflicts)) { /** @var IStorage $userStorage */ $userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage(); $userCache = $userStorage->getCache(); $i = 1; - $folderName = $folder['mount_point'] . ' (' . $i++ . ')'; + $folderName = $folder->mountPoint . ' (' . $i++ . ')'; while ($userCache->inCache("files/$folderName")) { $folderName = $originalFolderName . ' (' . $i++ . ')'; @@ -101,13 +100,13 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader): array { } return $this->getMount( - $folder['folder_id'], - '/' . $user->getUID() . '/files/' . $folder['mount_point'], - $folder['permissions'], - $folder['quota'], - $folder['rootCacheEntry'], + $folder->folderId, + '/' . $user->getUID() . '/files/' . $folder->mountPoint, + $folder->permissions, + $folder->quota, + $folder->rootCacheEntry, $loader, - $folder['acl'], + $folder->acl, $user, $aclManager, $rootRules diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php new file mode 100644 index 000000000..72c595c6c --- /dev/null +++ b/lib/ResponseDefinitions.php @@ -0,0 +1,49 @@ +|\stdClass, + * quota: int, + * size: int, + * acl: bool, + * manage: list, + * } + */ +class ResponseDefinitions { +} diff --git a/lib/Service/FoldersFilter.php b/lib/Service/FoldersFilter.php index ee587942a..635d6e600 100644 --- a/lib/Service/FoldersFilter.php +++ b/lib/Service/FoldersFilter.php @@ -7,9 +7,13 @@ namespace OCA\GroupFolders\Service; +use OCA\GroupFolders\ResponseDefinitions; use OCP\IGroupManager; use OCP\IUserSession; +/** + * @psalm-import-type GroupFoldersFolder from ResponseDefinitions + */ class FoldersFilter { public function __construct( private IUserSession $userSession, @@ -19,7 +23,7 @@ public function __construct( /** * @param array $folders List of all folders - * @return array List of folders that the api user can access + * @return list */ public function getForApiUser(array $folders): array { $user = $this->userSession->getUser(); @@ -27,7 +31,7 @@ public function getForApiUser(array $folders): array { return []; } - return array_filter($folders, function (array $folder) use ($user): bool { + return array_values(array_filter($folders, function (array $folder) use ($user): bool { foreach ($folder['manage'] as $manager) { if ($manager['type'] === 'group') { if ($this->groupManager->isInGroup($user->getUid(), $manager['id'])) { @@ -39,6 +43,6 @@ public function getForApiUser(array $folders): array { } return false; - }); + })); } } diff --git a/lib/Trash/TrashBackend.php b/lib/Trash/TrashBackend.php index 10a9adf93..1f6aa5793 100644 --- a/lib/Trash/TrashBackend.php +++ b/lib/Trash/TrashBackend.php @@ -16,7 +16,6 @@ use OCA\GroupFolders\Mount\MountProvider; use OCA\GroupFolders\Versions\VersionsBackend; use OCP\Constants; -use OCP\Files\Cache\ICacheEntry; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -244,7 +243,7 @@ private function unwrapJails(IStorage $storage, string $internalPath): array { private function userHasAccessToFolder(IUser $user, int $folderId): bool { $folders = $this->folderManager->getFoldersForUser($user); - $folderIds = array_map(fn (array $folder): int => $folder['folder_id'], $folders); + $folderIds = array_map(fn (\OCA\GroupFolders\Folder\Folder $folder): int => $folder->folderId, $folders); return in_array($folderId, $folderIds); } @@ -264,7 +263,7 @@ private function getNodeForTrashItem(IUser $user, ITrashItem $trashItem): ?Node [, $folderId, $path] = explode('/', $trashItem->getTrashPath(), 3); $folders = $this->folderManager->getFoldersForUser($user); foreach ($folders as $groupFolder) { - if ($groupFolder['folder_id'] === (int)$folderId) { + if ($groupFolder->folderId === (int)$folderId) { $trashRoot = $this->getTrashFolder((int)$folderId); try { $node = $trashRoot->get($path); @@ -308,11 +307,11 @@ private function getTrashFolder(int $folderId): Folder { } /** - * @param array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?ICacheEntry}[] $folders + * @param list<\OCA\GroupFolders\Folder\Folder> $folders * @return list */ private function getTrashForFolders(IUser $user, array $folders): array { - $folderIds = array_map(fn (array $folder): int => $folder['folder_id'], $folders); + $folderIds = array_map(fn (\OCA\GroupFolders\Folder\Folder $folder): int => $folder->folderId, $folders); $rows = $this->trashManager->listTrashForFolders($folderIds); $indexedRows = []; $trashItemsByOriginalLocation = []; @@ -324,9 +323,9 @@ private function getTrashForFolders(IUser $user, array $folders): array { $items = []; foreach ($folders as $folder) { - $folderId = $folder['folder_id']; - $folderHasAcl = $folder['acl']; - $mountPoint = $folder['mount_point']; + $folderId = $folder->folderId; + $folderHasAcl = $folder->acl; + $mountPoint = $folder->mountPoint; $trashFolder = $this->getTrashFolder($folderId); $content = $trashFolder->getDirectoryListing(); $userCanManageAcl = $this->folderManager->canManageACL($folderId, $user); diff --git a/lib/Versions/GroupVersionsExpireManager.php b/lib/Versions/GroupVersionsExpireManager.php index 6ba1a35bf..2af27fe20 100644 --- a/lib/Versions/GroupVersionsExpireManager.php +++ b/lib/Versions/GroupVersionsExpireManager.php @@ -14,11 +14,15 @@ use OCA\GroupFolders\Event\GroupVersionsExpireDeleteVersionEvent; use OCA\GroupFolders\Event\GroupVersionsExpireEnterFolderEvent; use OCA\GroupFolders\Folder\FolderManager; +use OCA\GroupFolders\ResponseDefinitions; use OCP\AppFramework\Utility\ITimeFactory; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\FileInfo; use OCP\IUser; +/** + * @psalm-import-type GroupFoldersFolder from ResponseDefinitions + */ class GroupVersionsExpireManager { public function __construct( private FolderManager $folderManager, @@ -45,7 +49,7 @@ public function expireFolders(array $folders): void { } /** - * @param array{acl: bool, groups: array>, id: int, mount_point: mixed, quota: int, size: 0} $folder + * @param GroupFoldersFolder $folder */ public function expireFolder(array $folder): void { $view = new View('/__groupfolders/versions/' . $folder['id']); diff --git a/lib/Versions/VersionsBackend.php b/lib/Versions/VersionsBackend.php index 471f5f968..45ba77a95 100644 --- a/lib/Versions/VersionsBackend.php +++ b/lib/Versions/VersionsBackend.php @@ -19,6 +19,7 @@ use OCA\GroupFolders\Mount\GroupFolderStorage; use OCA\GroupFolders\Mount\GroupMountPoint; use OCA\GroupFolders\Mount\MountProvider; +use OCA\GroupFolders\ResponseDefinitions; use OCP\Constants; use OCP\Files\File; use OCP\Files\FileInfo; @@ -32,6 +33,9 @@ use OCP\IUserSession; use Psr\Log\LoggerInterface; +/** + * @psalm-import-type GroupFoldersFolder from ResponseDefinitions + */ class VersionsBackend implements IVersionBackend, IMetadataVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IVersionsImporterBackend { public function __construct( private IRootFolder $rootFolder, @@ -240,7 +244,7 @@ public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): Fi } /** - * @param array{id: int, mount_point: string, groups: array|mixed, quota: mixed, size: int, acl: bool} $folder + * @param GroupFoldersFolder $folder * @return array */ public function getAllVersionedFiles(array $folder): array { diff --git a/openapi.json b/openapi.json new file mode 100644 index 000000000..8b3cee6e1 --- /dev/null +++ b/openapi.json @@ -0,0 +1,1858 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "groupfolders", + "version": "0.0.1", + "description": "Admin configured folders shared with everyone in a group", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "AclManage": { + "type": "object", + "required": [ + "displayName", + "id", + "type" + ], + "properties": { + "displayName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "user", + "group" + ] + } + } + }, + "Applicable": { + "type": "object", + "required": [ + "displayName", + "permissions", + "type" + ], + "properties": { + "displayName": { + "type": "string" + }, + "permissions": { + "type": "integer", + "format": "int64" + }, + "type": { + "type": "string", + "enum": [ + "group", + "circle" + ] + } + } + }, + "Capabilities": { + "type": "object", + "properties": { + "groupfolders": { + "type": "object", + "required": [ + "appVersion", + "hasGroupFolders" + ], + "properties": { + "appVersion": { + "type": "string" + }, + "hasGroupFolders": { + "type": "boolean" + } + } + } + } + }, + "Circle": { + "type": "object", + "required": [ + "singleId", + "displayName" + ], + "properties": { + "singleId": { + "type": "string" + }, + "displayName": { + "type": "string" + } + } + }, + "Folder": { + "type": "object", + "required": [ + "id", + "mount_point", + "groups", + "quota", + "size", + "acl", + "manage" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "mount_point": { + "type": "string" + }, + "groups": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Applicable" + } + }, + "quota": { + "type": "integer", + "format": "int64" + }, + "size": { + "type": "integer", + "format": "int64" + }, + "acl": { + "type": "boolean" + }, + "manage": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AclManage" + } + } + } + }, + "Group": { + "type": "object", + "required": [ + "gid", + "displayName" + ], + "properties": { + "gid": { + "type": "string" + }, + "displayName": { + "type": "string" + } + } + }, + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + }, + "User": { + "type": "object", + "required": [ + "uid", + "displayName" + ], + "properties": { + "uid": { + "type": "string" + }, + "displayName": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/groupfolders/delegation/groups": { + "get": { + "operationId": "delegation-get-all-groups", + "summary": "Returns the list of all groups", + "tags": [ + "delegation" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "All groups returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Group" + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/delegation/circles": { + "get": { + "operationId": "delegation-get-all-circles", + "summary": "Returns the list of all visible circles", + "tags": [ + "delegation" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "All circles returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Circle" + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/delegation/authorized-groups": { + "get": { + "operationId": "delegation-get-authorized-groups", + "summary": "Get the list Groups related to classname.", + "tags": [ + "delegation" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "classname", + "in": "query", + "description": "If the classname is - OCA\\GroupFolders\\Settings\\Admin : It's reference to fields in Admin Priveleges. - OCA\\GroupFolders\\Controller\\DelegationController : It's just to specific the subadmins. They can only manage groupfolders in which they are added in the Advanced Permissions (groups only)", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Authorized groups returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Group" + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders": { + "get": { + "operationId": "folder-get-folders", + "summary": "Gets all Groupfolders", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "applicable", + "in": "query", + "description": "Filter by applicable groups", + "schema": { + "type": "integer", + "default": 0, + "enum": [ + 0, + 1 + ] + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Groupfolders returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Folder" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Storage not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "folder-add-folder", + "summary": "Add a new Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "mountpoint" + ], + "properties": { + "mountpoint": { + "type": "string", + "description": "Mountpoint of the new Groupfolder" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Groupfolder added successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Folder" + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}": { + "get": { + "operationId": "folder-get-folder", + "summary": "Gets a Groupfolder by ID", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Groupfolder returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Folder" + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "folder-remove-folder", + "summary": "Remove a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Groupfolder removed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/groups": { + "post": { + "operationId": "folder-add-group", + "summary": "Add access of a group for a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "group" + ], + "properties": { + "group": { + "type": "string", + "description": "Group to add access for" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Group access added sucessfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/groups/{group}": { + "delete": { + "operationId": "folder-remove-group", + "summary": "Remove access of a group from a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "group", + "in": "path", + "description": "Group to remove access from", + "required": true, + "schema": { + "type": "string", + "pattern": "^.+$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Group access removed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "folder-set-permissions", + "summary": "Set the permissions of a group for a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "permissions": { + "type": "integer", + "format": "int64", + "description": "New permissions" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "group", + "in": "path", + "description": "Group for which the permissions will be set", + "required": true, + "schema": { + "type": "string", + "pattern": "^.+$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Permissions updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/manageACL": { + "post": { + "operationId": "folder-set-manageacl", + "summary": "Updates an ACL mapping", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "mappingType", + "mappingId", + "manageAcl" + ], + "properties": { + "mappingType": { + "type": "string", + "description": "Type of the ACL mapping" + }, + "mappingId": { + "type": "string", + "description": "ID of the ACL mapping" + }, + "manageAcl": { + "type": "boolean", + "description": "Whether to enable or disable the ACL mapping" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "ACL mapping updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/quota": { + "post": { + "operationId": "folder-set-quota", + "summary": "Set a new quota for a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "quota" + ], + "properties": { + "quota": { + "type": "integer", + "format": "int64", + "description": "New quota in bytes" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "New quota set successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/acl": { + "post": { + "operationId": "folder-setacl", + "summary": "Toggle the ACL for a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "acl" + ], + "properties": { + "acl": { + "type": "boolean", + "description": "Whether ACL should be enabled or not" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "ACL toggled successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/mountpoint": { + "post": { + "operationId": "folder-rename-folder", + "summary": "Rename a Groupfolder", + "description": "This endpoint requires password confirmation", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "mountpoint" + ], + "properties": { + "mountpoint": { + "type": "string", + "description": "New Mountpoint of the Groupfolder" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Groupfolder renamed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Groupfolder not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/groupfolders/folders/{id}/search": { + "get": { + "operationId": "folder-acl-mapping-search", + "summary": "Searches for matching ACL mappings", + "tags": [ + "folder" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The ID of the Groupfolder", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "search", + "in": "query", + "description": "String to search by", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "ACL Mappings returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "users", + "groups" + ], + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Group" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/package-lock.json b/package-lock.json index 55a3289b8..1b62849a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "jest": "^29.4.1", "jest-environment-jsdom": "^29.4.1", "module-replace-webpack-plugin": "0.0.12", + "openapi-typescript": "^7.4.1", "react-hot-loader": "4.13.1", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", @@ -4673,6 +4674,174 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/config": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.12.1.tgz", + "integrity": "sha512-RW3rSirfsPdr0uvATijRDU3f55SuZV3m7/ppdTDvGw4IB0cmeZRkFmqTrchxMqWP50Gfg1tpHnjdxUCNo0E2qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.25.4.tgz", + "integrity": "sha512-qnpr4Z1rzfXdtxQxt/lfGD0wW3UVrm3qhrTpzLG5R/Ze+z+1u8sSRiQHp9N+RT3IuMjh00wq59nop9x9PPa1jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "^8.11.2", + "@redocly/config": "^0.12.1", + "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.4", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^5.0.1", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=14.19.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@redocly/openapi-core/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@redocly/openapi-core/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@redocly/openapi-core/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@redocly/openapi-core/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@redocly/openapi-core/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@redocly/openapi-core/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@shikijs/core": { "version": "1.17.6", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.17.6.tgz", @@ -6333,7 +6502,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -7678,6 +7846,13 @@ "node": ">=0.8.0" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/char-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", @@ -13459,6 +13634,19 @@ "node": ">=0.8.19" } }, + "node_modules/index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -16161,6 +16349,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16561,6 +16759,13 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -18491,6 +18696,71 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-typescript": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.4.1.tgz", + "integrity": "sha512-HrRoWveViADezHCNgQqZmPKmQ74q7nuH/yg9ursFucZaYQNUqsX38fE/V2sKBHVM+pws4tAHpuh/ext2UJ/AoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.25.3", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.1.0", + "supports-color": "^9.4.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/openapi-typescript/node_modules/parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-typescript/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/openapi-typescript/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -20130,7 +20400,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23333,6 +23602,13 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, "node_modules/url": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", @@ -24518,6 +24794,13 @@ "node": ">= 6" } }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -24536,11 +24819,12 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/yargs-parser": { + "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 4affb73da..6ac7eed5e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix", "cypress": "npm run cypress:e2e", "cypress:e2e": "cypress run --e2e", - "cypress:gui": "cypress open" + "cypress:gui": "cypress open", + "typescript:generate": "npx openapi-typescript ./openapi.json -t -o src/types/openapi/openapi.ts" }, "engines": { "node": "^20.0.0", @@ -44,6 +45,7 @@ "jest": "^29.4.1", "jest-environment-jsdom": "^29.4.1", "module-replace-webpack-plugin": "0.0.12", + "openapi-typescript": "^7.4.1", "react-hot-loader": "4.13.1", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", diff --git a/src/components/SharingSidebarView.vue b/src/components/SharingSidebarView.vue index f31c0a087..ddc27ff9f 100644 --- a/src/components/SharingSidebarView.vue +++ b/src/components/SharingSidebarView.vue @@ -285,8 +285,8 @@ export default { unique: 'group:' + group.gid, type: 'group', id: group.gid, - displayname: group.displayname, - label: this.getFullDisplayName(group.displayname, 'group'), + displayName: group.displayName, + label: this.getFullDisplayName(group.displayName, 'group'), } }) const users = Object.values(result.data.ocs.data.users).map((user) => { @@ -294,8 +294,8 @@ export default { unique: 'user:' + user.uid, type: 'user', id: user.uid, - displayname: user.displayname, - label: this.getFullDisplayName(user.displayname, 'user'), + displayName: user.displayName, + label: this.getFullDisplayName(user.displayName, 'user'), } }) this.options = [...groups, ...users].filter((entry) => { @@ -319,7 +319,7 @@ export default { createAcl(option) { this.value = null const rule = new Rule() - rule.fromValues(option.type, option.id, option.displayname, 0b00000, 0b11111) + rule.fromValues(option.type, option.id, option.displayName, 0b00000, 0b11111) this.list.push(rule) client.propPatch(this.model, this.list.filter(rule => !rule.inherited)).then(() => { this.showAclCreate = false diff --git a/src/settings/AdminGroupSelect.tsx b/src/settings/AdminGroupSelect.tsx index f6c2824fa..cda0cd916 100644 --- a/src/settings/AdminGroupSelect.tsx +++ b/src/settings/AdminGroupSelect.tsx @@ -8,7 +8,8 @@ import Select from 'react-select' import { CLASS_NAME_ADMIN_DELEGATION } from '../Constants.js' import { Component } from 'react' import { getCurrentUser } from '@nextcloud/auth' -import { Group, Api } from './Api' +import { Api } from './Api' +import type { Group } from '../types' interface AdminGroupSelectProps { groups: Group[], diff --git a/src/settings/Api.ts b/src/settings/Api.ts index 0ff560d82..5e14390fb 100644 --- a/src/settings/Api.ts +++ b/src/settings/Api.ts @@ -7,42 +7,7 @@ import axios from '@nextcloud/axios' import { confirmPassword } from '@nextcloud/password-confirmation' // eslint-disable-next-line n/no-unpublished-import import type { OCSResponse } from '@nextcloud/typings/lib/ocs' - -export interface Group { - gid: string; - displayName: string; -} - -export interface Circle { - singleId: string; - displayName: string; -} - -export interface OCSUser { - uid: string; - displayname: string; -} - -export interface OCSGroup { - gid: string; - displayname: string; -} - -export interface ManageRuleProps { - type: string; - id: string; - displayname: string; -} - -export interface Folder { - id: number; - mount_point: string; - quota: number; - size: number; - groups: { [group: string]: number }; - acl: boolean; - manage: ManageRuleProps[]; -} +import type { Folder, Group, Circle, User, AclManage } from '../types' export class Api { @@ -143,23 +108,23 @@ export class Api { } async aclMappingSearch(folderId: number, search: string): Promise<{ - groups: ManageRuleProps[], - users: ManageRuleProps[] + groups: AclManage[], + users: AclManage[] }> { - const response = await axios.get>(this.getUrl(`folders/${folderId}/search`), { params: { search } }) + const response = await axios.get>(this.getUrl(`folders/${folderId}/search`), { params: { search } }) return { groups: Object.values(response.data.ocs.data.groups).map((item) => { return { type: 'group', id: item.gid, - displayname: item.displayname, + displayName: item.displayName, } }), users: Object.values(response.data.ocs.data.users).map((item) => { return { type: 'user', id: item.uid, - displayname: item.displayname, + displayName: item.displayName, } }), } diff --git a/src/settings/App.tsx b/src/settings/App.tsx index aafc1595c..453fada5f 100644 --- a/src/settings/App.tsx +++ b/src/settings/App.tsx @@ -5,7 +5,8 @@ import * as React from 'react' import { Component, FormEvent } from 'react' -import { Api, Circle, Folder, Group, ManageRuleProps } from './Api' +import { Api } from './Api' +import type { Circle, Folder, Group, AclManage } from '../types' import { FolderGroups } from './FolderGroups' import { QuotaSelect } from './QuotaSelect' import './App.scss' @@ -30,7 +31,7 @@ export type SortKey = 'mount_point' | 'quota' | 'groups' | 'acl'; export interface AppState { delegatedAdminGroups: Group[], delegatedSubAdminGroups: Group[], - folders: Folder[]; + folders: {[folderId: number]: Folder}; groups: Group[], circles: Circle[], newMountPoint: string; @@ -67,7 +68,7 @@ export class App extends Component implements OC.Plugin { - this.setState({ folders }) + this.setState({ folders: Object.fromEntries(folders.map((folder) => [folder.id, folder])) }) }) this.api.listGroups().then((groups) => { this.setState({ groups }) @@ -91,7 +92,7 @@ export class App extends Component implements OC.Plugin { const folders = this.state.folders - folders.push(folder) + folders[folder.id] = folder this.setState({ folders }) }) } @@ -108,7 +109,7 @@ export class App extends Component implements OC.Plugin { if (confirmed) { - this.setState({ folders: this.state.folders.filter(item => item.id !== folder.id) }) + this.setState({ folders: Object.fromEntries(Object.values(this.state.folders).filter(item => item.id !== folder.id).map((folder) => [folder.id, folder])) }) this.api.deleteFolder(folder.id) } }, @@ -118,7 +119,11 @@ export class App extends Component implements OC.Plugin implements OC.Plugin implements OC.Plugin { if (this.state.filter === '') { return true @@ -366,7 +371,7 @@ export class App extends Component implements OC.Plugin void; - onSearch: (name: string) => Promise<{ groups: ManageRuleProps[]; users: ManageRuleProps[]; }>; + onSearch: (name: string) => Promise<{ groups: AclManage[]; users: AclManage[]; }>; } // eslint-disable-next-line jsdoc/require-jsdoc @@ -398,7 +403,7 @@ function ManageAclSelect({ onChange, onSearch, folder }: ManageAclSelectProps) { } }} placeholder={t('groupfolders', 'Users/groups that can manage')} - getOptionLabel={(option) => `${option.displayname} (${typeLabel(option)})`} + getOptionLabel={(option) => `${option.displayName} (${typeLabel(option)})`} getOptionValue={(option) => option.type + '/' + option.id } styles={{ control: base => ({ diff --git a/src/settings/FolderGroups.tsx b/src/settings/FolderGroups.tsx index 64d3036d1..d017b46e7 100644 --- a/src/settings/FolderGroups.tsx +++ b/src/settings/FolderGroups.tsx @@ -7,16 +7,16 @@ import * as React from 'react' import Select from 'react-select' import './FolderGroups.scss' -import { Circle, Group } from './Api' +import { Applicable, Circle, Group } from '../types' import { loadState } from '@nextcloud/initial-state' // eslint-disable-next-line jsdoc/require-jsdoc -function hasPermissions(value: number, check: number): boolean { - return (value & check) === check +function hasPermissions(applicable: Applicable, check: number): boolean { + return (applicable.permissions & check) === check } export interface FolderGroupsProps { - groups: { [group: string]: number }, + groups: { [group: string]: Applicable; }, allCircles?: Circle[], allGroups?: Group[], onAddGroup: (name: string) => void; @@ -44,28 +44,28 @@ export function FolderGroups({ groups, allGroups = [], allCircles = [], onAddGro if (edit) { const setPermissions = (change: number, groupId: string): void => { - const newPermissions = groups[groupId] ^ change + const newPermissions = groups[groupId].permissions ^ change onSetPermissions(groupId, newPermissions) } const rows = Object.keys(groups).map((groupId, index) => { - const permissions = groups[groupId] + const applicable = groups[groupId] return {displayNames[index]} + checked={hasPermissions(applicable, (OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE))}/> + checked={hasPermissions(applicable, OC.PERMISSION_SHARE)}/> + checked={hasPermissions(applicable, (OC.PERMISSION_DELETE))}/> diff --git a/src/settings/SubAdminGroupSelect.tsx b/src/settings/SubAdminGroupSelect.tsx index 251705bc8..1c8177040 100644 --- a/src/settings/SubAdminGroupSelect.tsx +++ b/src/settings/SubAdminGroupSelect.tsx @@ -6,7 +6,8 @@ import * as React from 'react' import Select from 'react-select' import { getCurrentUser } from '@nextcloud/auth' import { Component } from 'react' -import { Group, Api } from './Api' +import { Api } from './Api' +import type { Group } from '../types' import { CLASS_NAME_SUBADMIN_DELEGATION } from '../Constants.js' interface SubAdminGroupSelectProps { diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..dda65d996 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,13 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { components } from './openapi/openapi.ts' + +export type Folder = components['schemas']['Folder']; +export type Group = components['schemas']['Group']; +export type Circle = components['schemas']['Circle']; +export type User = components['schemas']['User']; +export type AclManage = components['schemas']['AclManage']; +export type Applicable = components['schemas']['Applicable']; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts new file mode 100644 index 000000000..707cccfac --- /dev/null +++ b/src/types/openapi/openapi.ts @@ -0,0 +1,1017 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export type paths = { + "/ocs/v2.php/apps/groupfolders/delegation/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Returns the list of all groups */ + get: operations["delegation-get-all-groups"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/delegation/circles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Returns the list of all visible circles */ + get: operations["delegation-get-all-circles"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/delegation/authorized-groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the list Groups related to classname. */ + get: operations["delegation-get-authorized-groups"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Gets all Groupfolders */ + get: operations["folder-get-folders"]; + put?: never; + /** + * Add a new Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-add-folder"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Gets a Groupfolder by ID */ + get: operations["folder-get-folder"]; + put?: never; + post?: never; + /** + * Remove a Groupfolder + * @description This endpoint requires password confirmation + */ + delete: operations["folder-remove-folder"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Add access of a group for a Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-add-group"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/groups/{group}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Set the permissions of a group for a Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-set-permissions"]; + /** + * Remove access of a group from a Groupfolder + * @description This endpoint requires password confirmation + */ + delete: operations["folder-remove-group"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/manageACL": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Updates an ACL mapping + * @description This endpoint requires password confirmation + */ + post: operations["folder-set-manageacl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/quota": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Set a new quota for a Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-set-quota"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/acl": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Toggle the ACL for a Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-setacl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/mountpoint": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Rename a Groupfolder + * @description This endpoint requires password confirmation + */ + post: operations["folder-rename-folder"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/groupfolders/folders/{id}/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Searches for matching ACL mappings */ + get: operations["folder-acl-mapping-search"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +}; +export type webhooks = Record; +export type components = { + schemas: { + AclManage: { + displayName: string; + id: string; + /** @enum {string} */ + type: "user" | "group"; + }; + Applicable: { + displayName: string; + /** Format: int64 */ + permissions: number; + /** @enum {string} */ + type: "group" | "circle"; + }; + Capabilities: { + groupfolders?: { + appVersion: string; + hasGroupFolders: boolean; + }; + }; + Circle: { + singleId: string; + displayName: string; + }; + Folder: { + /** Format: int64 */ + id: number; + mount_point: string; + groups: { + [key: string]: components["schemas"]["Applicable"]; + }; + /** Format: int64 */ + quota: number; + /** Format: int64 */ + size: number; + acl: boolean; + manage: components["schemas"]["AclManage"][]; + }; + Group: { + gid: string; + displayName: string; + }; + OCSMeta: { + status: string; + statuscode: number; + message?: string; + totalitems?: string; + itemsperpage?: string; + }; + User: { + uid: string; + displayName: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; +export type $defs = Record; +export interface operations { + "delegation-get-all-groups": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description All groups returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Group"][]; + }; + }; + }; + }; + }; + }; + "delegation-get-all-circles": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description All circles returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Circle"][]; + }; + }; + }; + }; + }; + }; + "delegation-get-authorized-groups": { + parameters: { + query?: { + /** @description If the classname is - OCA\GroupFolders\Settings\Admin : It's reference to fields in Admin Priveleges. - OCA\GroupFolders\Controller\DelegationController : It's just to specific the subadmins. They can only manage groupfolders in which they are added in the Advanced Permissions (groups only) */ + classname?: string; + }; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Authorized groups returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Group"][]; + }; + }; + }; + }; + }; + }; + "folder-get-folders": { + parameters: { + query?: { + /** @description Filter by applicable groups */ + applicable?: 0 | 1; + }; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Groupfolders returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Folder"][]; + }; + }; + }; + }; + /** @description Storage not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-add-folder": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Mountpoint of the new Groupfolder */ + mountpoint: string; + }; + }; + }; + responses: { + /** @description Groupfolder added successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Folder"]; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-get-folder": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Groupfolder returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Folder"]; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-remove-folder": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Groupfolder removed successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-add-group": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Group to add access for */ + group: string; + }; + }; + }; + responses: { + /** @description Group access added sucessfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-set-permissions": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + /** @description Group for which the permissions will be set */ + group: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description New permissions + */ + permissions: number; + }; + }; + }; + responses: { + /** @description Permissions updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-remove-group": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + /** @description Group to remove access from */ + group: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Group access removed successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-set-manageacl": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Type of the ACL mapping */ + mappingType: string; + /** @description ID of the ACL mapping */ + mappingId: string; + /** @description Whether to enable or disable the ACL mapping */ + manageAcl: boolean; + }; + }; + }; + responses: { + /** @description ACL mapping updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "text/plain": string; + }; + }; + }; + }; + "folder-set-quota": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description New quota in bytes + */ + quota: number; + }; + }; + }; + responses: { + /** @description New quota set successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-setacl": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Whether ACL should be enabled or not */ + acl: boolean; + }; + }; + }; + responses: { + /** @description ACL toggled successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-rename-folder": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description New Mountpoint of the Groupfolder */ + mountpoint: string; + }; + }; + }; + responses: { + /** @description Groupfolder renamed successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {boolean} */ + success: true; + }; + }; + }; + }; + }; + /** @description Groupfolder not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; + "folder-acl-mapping-search": { + parameters: { + query?: { + /** @description String to search by */ + search?: string; + }; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + /** @description The ID of the Groupfolder */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description ACL Mappings returned */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + users: components["schemas"]["User"][]; + groups: components["schemas"]["Group"][]; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/tests/Folder/FolderManagerTest.php b/tests/Folder/FolderManagerTest.php index 94ff9f377..92905058c 100644 --- a/tests/Folder/FolderManagerTest.php +++ b/tests/Folder/FolderManagerTest.php @@ -6,6 +6,7 @@ namespace OCA\GroupFolders\Tests\Folder; +use OCA\GroupFolders\Folder\Folder; use OCA\GroupFolders\Folder\FolderManager; use OCP\Constants; use OCP\EventDispatcher\IEventDispatcher; @@ -62,7 +63,7 @@ private function clean(): void { } private function assertHasFolders(array $folders): void { - $existingFolders = array_values($this->manager->getAllFolders()); + $existingFolders = $this->manager->getAllFolders(); usort($existingFolders, fn (array $a, array $b): int => strcmp($a['mount_point'], $b['mount_point'])); usort($folders, fn (array $a, array $b): int => strcmp($a['mount_point'], $b['mount_point'])); @@ -82,6 +83,9 @@ private function assertHasFolders(array $folders): void { foreach ($existingFolders as &$existingFolder) { unset($existingFolder['id']); + if (!is_array($existingFolder['groups'])) { + $existingFolder['groups'] = []; + } } $this->assertEquals($folders, $existingFolders); @@ -274,8 +278,8 @@ public function testGetFoldersForGroup(): void { $folders = $this->manager->getFoldersForGroup('g1'); $this->assertCount(1, $folders); $folder = $folders[0]; - $this->assertEquals('foo', $folder['mount_point']); - $this->assertEquals(2, $folder['permissions']); + $this->assertEquals('foo', $folder->mountPoint); + $this->assertEquals(2, $folder->permissions); } public function testGetFoldersForGroups(): void { @@ -287,8 +291,8 @@ public function testGetFoldersForGroups(): void { $folders = $this->manager->getFoldersForGroups(['g1']); $this->assertCount(1, $folders); $folder = $folders[0]; - $this->assertEquals('foo', $folder['mount_point']); - $this->assertEquals(2, $folder['permissions']); + $this->assertEquals('foo', $folder->mountPoint); + $this->assertEquals(2, $folder->permissions); } /** @@ -319,12 +323,14 @@ public function testGetFoldersForUserSimple(): void { ->onlyMethods(['getFoldersForGroups']) ->getMock(); - $folder = [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 31, - 'quota' => -3 - ]; + $folder = new Folder( + 1, + 'foo', + 31, + -3, + false, + null, + ); $manager->expects($this->once()) ->method('getFoldersForGroups') @@ -341,18 +347,22 @@ public function testGetFoldersForUserMerge(): void { ->onlyMethods(['getFoldersForGroups']) ->getMock(); - $folder1 = [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 3, - 'quota' => 1000 - ]; - $folder2 = [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 8, - 'quota' => 1000 - ]; + $folder1 = new Folder( + 1, + 'foo', + 3, + 1000, + false, + null, + ); + $folder2 = new Folder( + 1, + 'foo', + 8, + 1000, + false, + null, + ); $manager->expects($this->any()) ->method('getFoldersForGroups') @@ -360,12 +370,14 @@ public function testGetFoldersForUserMerge(): void { $folders = $manager->getFoldersForUser($this->getUser(['g1', 'g2', 'g3'])); $this->assertEquals([ - [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 11, - 'quota' => 1000 - ] + new Folder( + 1, + 'foo', + 11, + 1000, + false, + null, + ), ], $folders); } @@ -376,18 +388,22 @@ public function testGetFolderPermissionsForUserMerge(): void { ->onlyMethods(['getFoldersForGroups']) ->getMock(); - $folder1 = [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 3, - 'quota' => 1000 - ]; - $folder2 = [ - 'folder_id' => 1, - 'mount_point' => 'foo', - 'permissions' => 8, - 'quota' => 1000 - ]; + $folder1 = new Folder( + 1, + 'foo', + 3, + 1000, + false, + null, + ); + $folder2 = new Folder( + 1, + 'foo', + 8, + 1000, + false, + null, + ); $manager->expects($this->any()) ->method('getFoldersForGroups') diff --git a/vendor-bin/openapi-extractor/composer.json b/vendor-bin/openapi-extractor/composer.json new file mode 100644 index 000000000..71320cbc6 --- /dev/null +++ b/vendor-bin/openapi-extractor/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "nextcloud/openapi-extractor": "^1.0" + } +} diff --git a/vendor-bin/openapi-extractor/composer.lock b/vendor-bin/openapi-extractor/composer.lock new file mode 100644 index 000000000..a8f4bd5b4 --- /dev/null +++ b/vendor-bin/openapi-extractor/composer.lock @@ -0,0 +1,240 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6964fdd288b96adcca764f7b24d4d678", + "packages": [], + "packages-dev": [ + { + "name": "adhocore/cli", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/adhocore/php-cli.git", + "reference": "57834cbaa4fb68cda849417ab86577fba2b15298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adhocore/php-cli/zipball/57834cbaa4fb68cda849417ab86577fba2b15298", + "reference": "57834cbaa4fb68cda849417ab86577fba2b15298", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ahc\\Cli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jitendra Adhikari", + "email": "jiten.adhikary@gmail.com" + } + ], + "description": "Command line interface library for PHP", + "keywords": [ + "argument-parser", + "argv-parser", + "cli", + "cli-action", + "cli-app", + "cli-color", + "cli-option", + "cli-writer", + "command", + "console", + "console-app", + "php-cli", + "php8", + "stream-input", + "stream-output" + ], + "support": { + "issues": "https://github.com/adhocore/php-cli/issues", + "source": "https://github.com/adhocore/php-cli/tree/v1.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/ji10", + "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" + } + ], + "time": "2024-09-05T00:08:47+00:00" + }, + { + "name": "nextcloud/openapi-extractor", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nextcloud-releases/openapi-extractor.git", + "reference": "88e347097db28b6e3f8f3c221502b80a4f455b1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud-releases/openapi-extractor/zipball/88e347097db28b6e3f8f3c221502b80a4f455b1f", + "reference": "88e347097db28b6e3f8f3c221502b80a4f455b1f", + "shasum": "" + }, + "require": { + "adhocore/cli": "^1.7", + "ext-simplexml": "*", + "nikic/php-parser": "^5.0", + "php": "^8.1", + "phpstan/phpdoc-parser": "^1.28" + }, + "require-dev": { + "nextcloud/coding-standard": "^1.2", + "nextcloud/ocp": "dev-master" + }, + "bin": [ + "generate-spec", + "merge-specs" + ], + "type": "library", + "autoload": { + "psr-4": { + "OpenAPIExtractor\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "description": "A tool for extracting OpenAPI specifications from Nextcloud source code", + "support": { + "issues": "https://github.com/nextcloud-releases/openapi-extractor/issues", + "source": "https://github.com/nextcloud-releases/openapi-extractor/tree/v1.0.0" + }, + "time": "2024-08-20T16:46:27+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "time": "2024-07-01T20:03:41+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" + }, + "time": "2024-09-07T20:13:05+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +}