├── .github ├── CODEOWNERS └── workflows │ ├── lint-info-xml.yml │ ├── lint-php-cs.yml │ ├── lint-php.yml │ ├── phpunit-mysql.yml │ ├── phpunit-oci.yml │ ├── phpunit-pgsql.yml │ ├── phpunit-sqlite.yml │ ├── phpunit-summary-when-unrelated.yml │ ├── pr-feedback.yml │ ├── psalm.yml │ └── reuse.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── AUTHORS.md ├── CHANGELOG.md ├── COPYING ├── LICENSES ├── AGPL-3.0-only.txt ├── AGPL-3.0-or-later.txt ├── BSD-3-Clause.txt ├── CC0-1.0.txt └── MIT.txt ├── Makefile ├── README.md ├── REUSE.toml ├── appinfo ├── info.xml └── routes.php ├── composer.json ├── composer.lock ├── lib ├── AppInfo │ └── Application.php ├── BackgroundJobs │ └── UpdateLookupServer.php ├── Command │ └── UsersUpdate.php ├── Controller │ ├── MasterController.php │ └── SlaveController.php ├── Exceptions │ ├── ConfigurationException.php │ └── MasterUrlException.php ├── GlobalSiteSelector.php ├── Listeners │ ├── AddContentSecurityPolicyListener.php │ ├── DeletingUser.php │ ├── UserCreated.php │ ├── UserDeleted.php │ ├── UserLoggedOut.php │ └── UserLoggingIn.php ├── Lookup.php ├── Master.php ├── Migration │ └── Version0110Date20180925143400.php ├── PublicCapabilities.php ├── Service │ └── SlaveService.php ├── Slave.php ├── TokenHandler.php ├── UserBackend.php ├── UserDiscoveryModules │ ├── IUserDiscoveryModule.php │ ├── ManualUserMapping.php │ ├── RemoteUserMapping.php │ ├── UserDiscoveryOIDC.php │ └── UserDiscoverySAML.php └── Vendor │ └── Firebase │ └── JWT │ ├── BeforeValidException.php │ ├── CachedKeySet.php │ ├── ExpiredException.php │ ├── JWK.php │ ├── JWT.php │ ├── JWTExceptionWithPayloadInterface.php │ ├── Key.php │ └── SignatureInvalidException.php ├── psalm.xml ├── tests ├── bootstrap.php ├── phpunit.xml ├── psalm-baseline.xml ├── stubs │ ├── doctrine_dbal_schema_abstractasset.php │ ├── doctrine_dbal_schema_column.php │ ├── doctrine_dbal_schema_schema.php │ ├── doctrine_dbal_schema_table.php │ ├── icewind_streams_directory.php │ ├── icewind_streams_iteratordirectory.php │ ├── icewind_streams_wrapperhandler.php │ ├── oc.php │ ├── oc_appframework_ocs_baseresponse.php │ ├── oc_appframework_ocs_v1response.php │ ├── oc_appframework_utility_simplecontainer.php │ ├── oc_core_command_base.php │ ├── oc_files_cache_cache.php │ ├── oc_files_cache_cacheentry.php │ ├── oc_files_cache_scanner.php │ ├── oc_files_cache_wrapper_cachejail.php │ ├── oc_files_cache_wrapper_cachewrapper.php │ ├── oc_files_filesystem.php │ ├── oc_files_mount_mountpoint.php │ ├── oc_files_node_lazyfolder.php │ ├── oc_files_node_node.php │ ├── oc_files_objectstore_objectstorescanner.php │ ├── oc_files_objectstore_objectstorestorage.php │ ├── oc_files_setupmanager.php │ ├── oc_files_storage_common.php │ ├── oc_files_storage_local.php │ ├── oc_files_storage_storage.php │ ├── oc_files_storage_temporary.php │ ├── oc_files_storage_wrapper_jail.php │ ├── oc_files_storage_wrapper_permissionsmask.php │ ├── oc_files_storage_wrapper_quota.php │ ├── oc_files_storage_wrapper_wrapper.php │ ├── oc_files_view.php │ ├── oc_group_database.php │ ├── oc_group_manager.php │ ├── oc_hooks_basicemitter.php │ ├── oc_hooks_emitter.php │ ├── oc_hooks_emittertrait.php │ ├── oc_hooks_publicemitter.php │ ├── oc_server.php │ ├── oc_servercontainer.php │ ├── oc_settings_authorizedgroupmapper.php │ ├── oc_user_user.php │ ├── oca_circles_circlesmanager.php │ ├── oca_circles_circlesqueryhelper.php │ ├── oca_circles_events_circledestroyedevent.php │ ├── oca_circles_events_circleresultgenericevent.php │ ├── oca_circles_exceptions_circlenotfoundexception.php │ ├── oca_circles_exceptions_federateditemexception.php │ ├── oca_circles_exceptions_federateditemnotfoundexception.php │ ├── oca_circles_ientity.php │ ├── oca_circles_ifederatedmodel.php │ ├── oca_circles_ifederateduser.php │ ├── oca_circles_iqueryprobe.php │ ├── oca_circles_model_circle.php │ ├── oca_circles_model_federateduser.php │ ├── oca_circles_model_managedmodel.php │ ├── oca_circles_model_member.php │ ├── oca_circles_model_probes_basicprobe.php │ ├── oca_circles_model_probes_circleprobe.php │ ├── oca_circles_model_probes_memberprobe.php │ ├── oca_circles_tools_db_iqueryrow.php │ ├── oca_circles_tools_ideserializable.php │ ├── oca_dav_connector_sabre_directory.php │ ├── oca_dav_connector_sabre_node.php │ ├── oca_dav_connector_sabre_principal.php │ ├── oca_files_event_loadadditionalscriptsevent.php │ ├── oca_files_sharing_event_beforetemplaterenderedevent.php │ ├── oca_files_sharing_external_mountprovider.php │ ├── oca_files_trashbin_expiration.php │ ├── oca_files_trashbin_trash_itrashbackend.php │ ├── oca_files_trashbin_trash_itrashitem.php │ ├── oca_files_trashbin_trash_trashitem.php │ ├── oca_files_versions_expiration.php │ ├── oca_files_versions_versions_ideletableversionbackend.php │ ├── oca_files_versions_versions_imetadataversion.php │ ├── oca_files_versions_versions_imetadataversionbackend.php │ ├── oca_files_versions_versions_ineedsyncversionbackend.php │ ├── oca_files_versions_versions_iversion.php │ ├── oca_files_versions_versions_iversionbackend.php │ ├── oca_files_versions_versions_iversionsimporterbackend.php │ ├── oca_files_versions_versions_version.php │ ├── oca_settings_service_authorizedgroupservice.php │ ├── ocp_files_storage_iconstructablestorage.php │ ├── stecman_component_symfony_console_bashcompletion_completion_completionawareinterface.php │ ├── symfony_component_console_command_command.php │ ├── symfony_component_console_helper_table.php │ ├── symfony_component_console_input_inputargument.php │ ├── symfony_component_console_input_inputinterface.php │ ├── symfony_component_console_input_inputoption.php │ ├── symfony_component_console_output_outputinterface.php │ ├── symfony_component_console_question_confirmationquestion.php │ ├── symfony_component_console_question_question.php │ ├── test_testcase.php │ └── test_traits_usertrait.php └── unit │ └── lib │ ├── Controller │ └── SlaveControllerTest.php │ ├── GlobalSiteSelectorTest.php │ ├── LookupTest.php │ └── MasterTest.php └── vendor-bin ├── csfixer ├── composer.json └── composer.lock ├── mozart ├── composer.json └── composer.lock ├── phpunit ├── composer.json └── composer.lock └── psalm ├── composer.json └── composer.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors 2 | # SPDX-License-Identifier: AGPL-3.0-or-later 3 | # App maintainers 4 | /appinfo/info.xml @ArtificialOwl 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/lint-info-xml.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Lint info.xml 10 | 11 | on: pull_request 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: lint-info-xml-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | xml-linters: 22 | runs-on: ubuntu-latest-low 23 | 24 | name: info.xml lint 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 28 | with: 29 | persist-credentials: false 30 | 31 | - name: Download schema 32 | run: wget https://raw.githubusercontent.com/nextcloud/appstore/master/nextcloudappstore/api/v1/release/info.xsd 33 | 34 | - name: Lint info.xml 35 | uses: ChristophWurst/xmllint-action@36f2a302f84f8c83fceea0b9c59e1eb4a616d3c1 # v1.2 36 | with: 37 | xml-file: ./appinfo/info.xml 38 | xml-schema-file: ./info.xsd 39 | -------------------------------------------------------------------------------- /.github/workflows/lint-php-cs.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Lint php-cs 10 | 11 | on: pull_request 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: lint-php-cs-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | lint: 22 | runs-on: ubuntu-latest 23 | 24 | name: php-cs 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 29 | with: 30 | persist-credentials: false 31 | 32 | - name: Get php version 33 | id: versions 34 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1 35 | 36 | - name: Set up php${{ steps.versions.outputs.php-min }} 37 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 38 | with: 39 | php-version: ${{ steps.versions.outputs.php-min }} 40 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite 41 | coverage: none 42 | ini-file: development 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | - name: Install dependencies 47 | run: | 48 | composer remove nextcloud/ocp --dev --no-scripts 49 | composer i 50 | 51 | - name: Lint 52 | run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 ) 53 | -------------------------------------------------------------------------------- /.github/workflows/lint-php.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Lint php 10 | 11 | on: pull_request 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: lint-php-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | matrix: 22 | runs-on: ubuntu-latest-low 23 | outputs: 24 | php-versions: ${{ steps.versions.outputs.php-versions }} 25 | steps: 26 | - name: Checkout app 27 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 28 | with: 29 | persist-credentials: false 30 | 31 | - name: Get version matrix 32 | id: versions 33 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.0.0 34 | 35 | php-lint: 36 | runs-on: ubuntu-latest 37 | needs: matrix 38 | strategy: 39 | matrix: 40 | php-versions: ${{fromJson(needs.matrix.outputs.php-versions)}} 41 | 42 | name: php-lint 43 | 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 47 | with: 48 | persist-credentials: false 49 | 50 | - name: Set up php ${{ matrix.php-versions }} 51 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 52 | with: 53 | php-version: ${{ matrix.php-versions }} 54 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite 55 | coverage: none 56 | ini-file: development 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Lint 61 | run: composer run lint 62 | 63 | summary: 64 | permissions: 65 | contents: none 66 | runs-on: ubuntu-latest-low 67 | needs: php-lint 68 | 69 | if: always() 70 | 71 | name: php-lint-summary 72 | 73 | steps: 74 | - name: Summary status 75 | run: if ${{ needs.php-lint.result != 'success' && needs.php-lint.result != 'skipped' }}; then exit 1; fi 76 | -------------------------------------------------------------------------------- /.github/workflows/phpunit-summary-when-unrelated.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: PHPUnit summary 10 | 11 | on: 12 | pull_request: 13 | paths-ignore: 14 | - '.github/workflows/**' 15 | - 'appinfo/**' 16 | - 'lib/**' 17 | - 'templates/**' 18 | - 'tests/**' 19 | - 'vendor/**' 20 | - 'vendor-bin/**' 21 | - '.php-cs-fixer.dist.php' 22 | - 'composer.json' 23 | - 'composer.lock' 24 | 25 | permissions: 26 | contents: read 27 | 28 | jobs: 29 | summary-mysql: 30 | permissions: 31 | contents: none 32 | runs-on: ubuntu-latest 33 | 34 | name: phpunit-mysql-summary 35 | 36 | steps: 37 | - name: Summary status 38 | run: 'echo "No PHP files changed, skipped PHPUnit"' 39 | 40 | summary-oci: 41 | permissions: 42 | contents: none 43 | runs-on: ubuntu-latest 44 | 45 | name: phpunit-oci-summary 46 | 47 | steps: 48 | - name: Summary status 49 | run: 'echo "No PHP files changed, skipped PHPUnit"' 50 | 51 | summary-pgsql: 52 | permissions: 53 | contents: none 54 | runs-on: ubuntu-latest 55 | 56 | name: phpunit-pgsql-summary 57 | 58 | steps: 59 | - name: Summary status 60 | run: 'echo "No PHP files changed, skipped PHPUnit"' 61 | 62 | summary-sqlite: 63 | permissions: 64 | contents: none 65 | runs-on: ubuntu-latest 66 | 67 | name: phpunit-sqlite-summary 68 | 69 | steps: 70 | - name: Summary status 71 | run: 'echo "No PHP files changed, skipped PHPUnit"' 72 | -------------------------------------------------------------------------------- /.github/workflows/pr-feedback.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | 6 | # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-FileCopyrightText: 2023 Marcel Klehr 8 | # SPDX-FileCopyrightText: 2023 Joas Schilling <213943+nickvergessen@users.noreply.github.com> 9 | # SPDX-FileCopyrightText: 2023 Daniel Kesselberg 10 | # SPDX-FileCopyrightText: 2023 Florian Steffens 11 | # SPDX-License-Identifier: MIT 12 | 13 | name: 'Ask for feedback on PRs' 14 | on: 15 | schedule: 16 | - cron: '30 1 * * *' 17 | 18 | permissions: 19 | contents: read 20 | pull-requests: write 21 | 22 | jobs: 23 | pr-feedback: 24 | if: ${{ github.repository_owner == 'nextcloud' }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: The get-github-handles-from-website action 28 | uses: marcelklehr/get-github-handles-from-website-action@06b2239db0a48fe1484ba0bfd966a3ab81a08308 # v1.0.1 29 | id: scrape 30 | with: 31 | website: 'https://nextcloud.com/team/' 32 | 33 | - name: Get blocklist 34 | id: blocklist 35 | run: | 36 | blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -) 37 | echo "blocklist=$blocklist" >> "$GITHUB_OUTPUT" 38 | 39 | - uses: nextcloud/pr-feedback-action@f0cab224dea8e1f282f9451de322f323c78fc7a5 # main 40 | with: 41 | feedback-message: | 42 | Hello there, 43 | Thank you so much for taking the time and effort to create a pull request to our Nextcloud project. 44 | 45 | We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process. 46 | 47 | Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6 48 | 49 | Thank you for contributing to Nextcloud and we hope to hear from you soon! 50 | 51 | (If you believe you should not receive this message, you can add yourself to the [blocklist](https://github.com/nextcloud/.github/blob/master/non-community-usernames.txt).) 52 | days-before-feedback: 14 53 | start-date: '2025-06-12' 54 | exempt-authors: '${{ steps.blocklist.outputs.blocklist }},${{ steps.scrape.outputs.users }}' 55 | exempt-bots: true 56 | -------------------------------------------------------------------------------- /.github/workflows/psalm.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Static analysis 10 | 11 | on: pull_request 12 | 13 | concurrency: 14 | group: psalm-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | static-analysis: 22 | runs-on: ubuntu-latest 23 | 24 | name: static-psalm-analysis 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 28 | with: 29 | persist-credentials: false 30 | 31 | - name: Get php version 32 | id: versions 33 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1 34 | 35 | - name: Check enforcement of minimum PHP version ${{ steps.versions.outputs.php-min }} in psalm.xml 36 | run: grep 'phpVersion="${{ steps.versions.outputs.php-min }}' psalm.xml 37 | 38 | - name: Set up php${{ steps.versions.outputs.php-available }} 39 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 40 | with: 41 | php-version: ${{ steps.versions.outputs.php-available }} 42 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite 43 | coverage: none 44 | ini-file: development 45 | # Temporary workaround for missing pcntl_* in PHP 8.3 46 | ini-values: disable_functions= 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | - name: Install dependencies 51 | run: | 52 | composer remove nextcloud/ocp --dev --no-scripts 53 | composer i 54 | 55 | - name: Check for vulnerable PHP dependencies 56 | run: composer require --dev roave/security-advisories:dev-latest 57 | 58 | - name: Install nextcloud/ocp 59 | run: composer require --dev nextcloud/ocp:dev-${{ steps.versions.outputs.branches-max }} --ignore-platform-reqs --with-dependencies 60 | 61 | - name: Run coding standards check 62 | run: composer run psalm -- --threads=1 --monochrome --no-progress --output-format=github 63 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | 6 | # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. 7 | # 8 | # SPDX-License-Identifier: CC0-1.0 9 | 10 | name: REUSE Compliance Check 11 | 12 | on: [pull_request] 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | reuse-compliance-check: 19 | runs-on: ubuntu-latest-low 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 23 | with: 24 | persist-credentials: false 25 | 26 | - name: REUSE Compliance Check 27 | uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors 2 | # SPDX-License-Identifier: AGPL-3.0-or-later 3 | *~ 4 | \#* 5 | build/ 6 | 7 | # kdevelop 8 | .kdev 9 | *.kdev4 10 | *.kate-swp 11 | 12 | # eclipse 13 | .project 14 | .settings 15 | 16 | # netbeans 17 | nbproject 18 | 19 | # phpStorm 20 | .idea 21 | *.iml 22 | 23 | # geany 24 | *.geany 25 | 26 | # Cloud9IDE 27 | .settings.xml 28 | .c9revisions 29 | 30 | # vim ex mode 31 | .vimrc 32 | 33 | # ack(-grep) 34 | .ackrc 35 | 36 | # Mac OS 37 | .DS_Store 38 | 39 | # WebFinger 40 | .well-known 41 | /.buildpath 42 | 43 | # composer 44 | /vendor 45 | /vendor-bin/*/vendor 46 | 47 | # Tests - auto-generated files 48 | /data-autotest 49 | /tests/coverage* 50 | /tests/css 51 | /tests/karma-coverage 52 | /tests/autoconfig* 53 | /tests/autotest* 54 | /tests/data/lorem-copy.txt 55 | /tests/data/testimage-copy.png 56 | /tests/unit/.phpunit.result.cache 57 | /config/config-autotest-backup.php 58 | /config/autoconfig.php 59 | clover.xml 60 | 61 | /.php_cs.cache 62 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | getFinder() 17 | ->notPath('build') 18 | ->notPath('tests/stubs') 19 | ->notPath('l10n') 20 | ->notPath('src') 21 | ->notPath('vendor') 22 | ->notPath('vendor-bin') 23 | ->notPath('lib/Vendor') 24 | ->in(__DIR__); 25 | return $config; 26 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | 5 | # Authors 6 | 7 | - Andy Scherzinger 8 | - Arthur Schiwon 9 | - Björn Schießle 10 | - Carl Schwan 11 | - Côme Chilliet 12 | - Grigorii K. Shartsev 13 | - Jason Bayton 14 | - Joas Schilling 15 | - John Molakvoæ 16 | - Jos Poortvliet 17 | - Julien Veyssier 18 | - Julius Knorr 19 | - Maxence Lange 20 | - Morris Jobke 21 | - navid 22 | - Roeland Jago Douma 23 | - Simon L 24 | - Tobias Kaminsky 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 5 | # Changelog 6 | All notable changes to this project will be documented in this file. 7 | 8 | ## 2.6.1 9 | 10 | - expecting a JSON as a result from POST request made to the Remote User Discovery endpoint 11 | - fixing an issue during app token creation with OIDC 12 | - adding a secret key to data sent to Remote User Discovery endpoint 13 | 14 | ## 2.6.0 15 | 16 | - support for OIDC (using app user_oidc v6.2.1 or superior) 17 | - new user discovery mapping 'RemoteUserDiscovery' to obtain instance from external service 18 | - new config 'gss.updatels.interval' to change update internal to lookup server 19 | - upgrade composer and clean code 20 | - compat nc31 21 | 22 | ## 2.5.2 23 | 24 | - get rid of getEventDispatcher() 25 | - compat nc30 26 | 27 | ## 2.5.1 28 | 29 | - ignore null user on logout 30 | - compat nc29 31 | 32 | ## 2.5.0 33 | 34 | - compat nc28 35 | 36 | ## 2.4.5 37 | 38 | - fix an issue in auth process on slave 39 | 40 | ## 2.4.4 41 | 42 | - fix a path lost during the redirection of a not logged account to master 43 | 44 | ## 2.4.3 45 | 46 | - sanitize uid while getting its location 47 | - encode uid on search on LUS 48 | - wrap JWT with mozart 49 | 50 | ## 2.4.2 51 | 52 | - add a settings to stay on slave if local account after logout 53 | - check instance type before updating user 54 | 55 | ## 2.4.1 56 | 57 | - passing idp to slave, back to master on logout. 58 | - storing idp in user preferences. 59 | 60 | ## 2.4.0 61 | 62 | - compat nc26 63 | 64 | ## 2.3.2 65 | 66 | - new app type extended_authentication 67 | 68 | ## 2.3.1 69 | 70 | - get full account data on login 71 | 72 | ## 2.3.0 73 | 74 | - new session is now generated on slave when using user_saml 75 | - fixed logout with SAML 76 | - fix a conflict with ldap authentication 77 | - retrieve and cache display names 78 | - more debug log 79 | - cleaning code 80 | 81 | ## 2.2.0 82 | 83 | - compat nc25 84 | - update user on login 85 | - fix an issue on logout with saml 86 | 87 | ## 2.1.1 88 | 89 | - config flag to ignore user's account properties 90 | 91 | ## 2.1.0 92 | 93 | ### Fixed 94 | 95 | - #37 get account data as array @blizzz 96 | - #56 Allow form action to handle nc:// protocol @juliushaertl 97 | - #51 Fix session token creation "remember" parameter @eneiluj 98 | 99 | ### Other 100 | 101 | - #40 configure Client to allow_local_remote_server based on 'gss.allow_local_address' @ArtificialOwl 102 | - #49 Implement csp allow list for master node @juliushaertl 103 | - #46 Github CI @juliushaertl 104 | - #36 Deprecations and cleanup @blizzz 105 | - #42 Update version on master @nickvergessen 106 | - #52 Use addServiceListener for registration with the IEventDispatcher @juliushaertl 107 | 108 | ## 1.3.0 109 | ### Added 110 | - NC19 compatible 111 | 112 | ## 1.2.1 113 | 114 | ### Added 115 | - Extra debug info 116 | - Added capabilities so the client knows to use the old flow 117 | - NC18 compatible 118 | 119 | ### Changed 120 | - Use longer generated apptokens 121 | 122 | ## 1.2.0 123 | 124 | ### Added 125 | - NC16 and NC17 compatibility 126 | - Added Regex matching for GSS nodes 127 | - Added debug log statements 128 | 129 | ### Fixed 130 | - Redirect users to GSS if they are not logged in 131 | - Fixed non email username mappiong 132 | - Fixed login flow with branded android clients 133 | 134 | ## 1.1.0 135 | 136 | ### Fixed 137 | 138 | - Fix client login with the new login flow 139 | - Fix client login when users add the username/password manually 140 | 141 | ### Changed 142 | 143 | - Remember exact link and redirect the user to the correct sub page on the client node 144 | - Provide the number of users known by the global scale user back-end 145 | 146 | ## 1.0.0 147 | 148 | ### Changed 149 | 150 | - First stable version of the Global Site Selector 151 | - Works with local user back-ends and SAML 152 | 153 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors 2 | # SPDX-License-Identifier: AGPL-3.0-or-later 3 | app_name=globalsiteselector 4 | 5 | project_dir=$(CURDIR)/../$(app_name) 6 | build_dir=$(CURDIR)/build/artifacts 7 | source_dir=$(build_dir)/source 8 | sign_dir=$(build_dir)/sign 9 | package_name=$(app_name) 10 | version+=2.7.0 11 | 12 | all: appstore 13 | 14 | clean: 15 | rm -rf $(build_dir) 16 | rm -fr vendor/ 17 | rm -fr vendor-bin/csfixer/vendor/ 18 | rm -fr vendor-bin/mozart/vendor/ 19 | rm -fr vendor-bin/phpunit/vendor/ 20 | rm -fr vendor-bin/psalm/vendor/ 21 | 22 | cs-check: composer-dev 23 | composer cs:check 24 | 25 | cs-fix: composer-dev 26 | composer cs:fix 27 | 28 | composer: 29 | composer install 30 | composer upgrade 31 | 32 | composer-dev: 33 | composer install --dev 34 | composer upgrade --dev 35 | 36 | appstore: clean composer release 37 | 38 | release: 39 | mkdir -p $(sign_dir) 40 | rsync -a \ 41 | --exclude=/build \ 42 | --exclude=/docs \ 43 | --exclude=/translationfiles \ 44 | --exclude=.tx \ 45 | --exclude=.idea \ 46 | --exclude=.php-cs-fixer.dist.php \ 47 | --exclude=.php-cs-fixer.cache \ 48 | --exclude=CHANGELOG.md \ 49 | --exclude=composer.json \ 50 | --exclude=composer.lock \ 51 | --exclude=psalm.xml \ 52 | --exclude=README.md \ 53 | --exclude=/tests \ 54 | --exclude=.git \ 55 | --exclude=.github \ 56 | --exclude=/l10n/l10n.pl \ 57 | --exclude=/CONTRIBUTING.md \ 58 | --exclude=/issue_template.md \ 59 | --exclude=.gitattributes \ 60 | --exclude=.gitignore \ 61 | --exclude=.scrutinizer.yml \ 62 | --exclude=vendor \ 63 | --exclude=vendor-bin \ 64 | --exclude=.travis.yml \ 65 | --exclude=/Makefile \ 66 | --exclude=.drone.yml \ 67 | $(project_dir)/ $(sign_dir)/$(app_name) 68 | tar -czf $(build_dir)/$(app_name)-$(version).tar.gz \ 69 | -C $(sign_dir) $(app_name) 70 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors 2 | # SPDX-License-Identifier: AGPL-3.0-or-later 3 | version = 1 4 | SPDX-PackageName = "globalsiteselector" 5 | SPDX-PackageSupplier = "Nextcloud " 6 | SPDX-PackageDownloadLocation = "https://github.com/nextcloud/globalsiteselector" 7 | 8 | [[annotations]] 9 | path = ["composer.json", "composer.lock"] 10 | precedence = "aggregate" 11 | SPDX-FileCopyrightText = "2017 Nextcloud GmbH and Nextcloud contributors" 12 | SPDX-License-Identifier = "AGPL-3.0-or-later" 13 | 14 | [[annotations]] 15 | path = ["vendor-bin/csfixer/composer.json", "vendor-bin/csfixer/composer.lock", "vendor-bin/mozart/composer.json", "vendor-bin/mozart/composer.lock", "vendor-bin/phpunit/composer.json", "vendor-bin/phpunit/composer.lock", "vendor-bin/psalm/composer.json", "vendor-bin/psalm/composer.lock", "composer.lock"] 16 | precedence = "aggregate" 17 | SPDX-FileCopyrightText = "2023 Nextcloud GmbH and Nextcloud contributors" 18 | SPDX-License-Identifier = "AGPL-3.0-or-later" 19 | 20 | [[annotations]] 21 | path = "lib/Vendor/Firebase/JWT/**.php" 22 | precedence = "aggregate" 23 | SPDX-FileCopyrightText = "2023 PHP-JWT contributors" 24 | SPDX-License-Identifier = "BSD-3-Clause" 25 | 26 | [[annotations]] 27 | path = ["tests/stubs/doctrine_dbal_**"] 28 | precedence = "aggregate" 29 | SPDX-FileCopyrightText = "none" 30 | SPDX-License-Identifier = "MIT" 31 | 32 | [[annotations]] 33 | path = ["tests/stubs/symfony_component_**"] 34 | precedence = "aggregate" 35 | SPDX-FileCopyrightText = "none" 36 | SPDX-License-Identifier = "MIT" 37 | 38 | [[annotations]] 39 | path = ["tests/stubs/stecman_component_**"] 40 | precedence = "aggregate" 41 | SPDX-FileCopyrightText = "none" 42 | SPDX-License-Identifier = "MIT" 43 | 44 | [[annotations]] 45 | path = ["tests/stubs/icewind_streams_**"] 46 | precedence = "aggregate" 47 | SPDX-FileCopyrightText = "none" 48 | SPDX-License-Identifier = "MIT" 49 | -------------------------------------------------------------------------------- /appinfo/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | globalsiteselector 8 | Global Site Selector 9 | Nextcloud Portal to redirect users to the right instance 10 | The Global Site Selector allows you to run multiple small Nextcloud instances and redirect users to the right server 11 | 2.7.0 12 | agpl 13 | Bjoern Schiessle 14 | Maxence Lange 15 | GlobalSiteSelector 16 | 17 | 18 | 19 | tools 20 | https://nextcloud.com/globalscale/ 21 | https://github.com/nextcloud/globalsiteselector/issues 22 | https://github.com/nextcloud/globalsiteselector 23 | 24 | 25 | 26 | 27 | OCA\GlobalSiteSelector\BackgroundJobs\UpdateLookupServer 28 | 29 | 30 | 31 | OCA\GlobalSiteSelector\Command\UsersUpdate 32 | 33 | 34 | -------------------------------------------------------------------------------- /appinfo/routes.php: -------------------------------------------------------------------------------- 1 | [ 11 | ['name' => 'Slave#createAppToken', 'url' => '/v1/createapptoken', 'verb' => 'GET'], 12 | ], 13 | 'routes' => [ 14 | [ 15 | 'name' => 'Slave#autoLogin', 16 | 'url' => '/autologin', 17 | 'verb' => 'GET' 18 | ], 19 | [ 20 | 'name' => 'Master#autoLogout', 21 | 'url' => '/autologout', 22 | 'verb' => 'GET' 23 | ], 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextcloud/globalsiteselector", 3 | "description": "globalsiteselector", 4 | "minimum-stability": "stable", 5 | "license": "agpl", 6 | "config": { 7 | "allow-plugins": { 8 | "bamarni/composer-bin-plugin": true 9 | }, 10 | "optimize-autoloader": true, 11 | "classmap-authoritative": true, 12 | "autoloader-suffix": "GlobalSiteSelector", 13 | "platform": { 14 | "php": "8.2" 15 | }, 16 | "sort-packages": true 17 | }, 18 | "authors": [ 19 | { 20 | "name": "Maxence Lange", 21 | "email": "maxence@artificial-owl.com" 22 | } 23 | ], 24 | "autoload": { 25 | "psr-4": { 26 | "OCA\\GlobalSiteSelector\\": "lib/" 27 | } 28 | }, 29 | "scripts": { 30 | "cs:fix": "./vendor-bin/csfixer/vendor/bin/php-cs-fixer fix", 31 | "cs:check": "./vendor-bin/csfixer/vendor/bin/php-cs-fixer fix --dry-run --diff", 32 | "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './tests/stubs/*' -print0 | xargs -0 -n1 php -l", 33 | "psalm": "./vendor-bin/psalm/vendor/bin/psalm --threads=1", 34 | "psalm:clear": "./vendor-bin/psalm/vendor/bin/psalm --clear-cache && ./vendor-bin/psalm/vendor/bin/psalm --clear-global-cache", 35 | "psalm:fix": "./vendor-bin/psalm/vendor/bin/psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", 36 | "test:unit": "./vendor-bin/phpunit/vendor/bin/phpunit -c tests/phpunit.xml --color --fail-on-warning --fail-on-risky", 37 | "rector": "rector && composer cs:fix", 38 | "post-install-cmd": [ 39 | "\"vendor-bin/mozart/vendor/bin/mozart\" compose", 40 | "@composer dump-autoload" 41 | ], 42 | "post-update-cmd": [ 43 | "\"vendor-bin/mozart/vendor/bin/mozart\" compose", 44 | "composer dump-autoload" 45 | ] 46 | }, 47 | "require": { 48 | "bamarni/composer-bin-plugin": "^1.8", 49 | "firebase/php-jwt": "^6.8" 50 | }, 51 | "extra": { 52 | "bamarni-bin": { 53 | "bin-links": false, 54 | "target-directory": "vendor-bin", 55 | "forward-command": true 56 | }, 57 | "mozart": { 58 | "dep_namespace": "OCA\\GlobalSiteSelector\\Vendor\\", 59 | "dep_directory": "/lib/Vendor/", 60 | "classmap_directory": "/lib/autoload/", 61 | "classmap_prefix": "GLOBALSITESELECTOR_", 62 | "packages": [ 63 | "firebase/php-jwt" 64 | ] 65 | } 66 | }, 67 | "require-dev": { 68 | "nextcloud/ocp": "dev-master" 69 | }, 70 | "autoload-dev": { 71 | "psr-4": { 72 | "OCP\\": "vendor/nextcloud/ocp/OCP" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/BackgroundJobs/UpdateLookupServer.php: -------------------------------------------------------------------------------- 1 | setInterval($config->getSystemValueInt('gss.updatels.interval', 86400)); 31 | $this->setTimeSensitivity(IJob::TIME_SENSITIVE); 32 | } 33 | 34 | protected function run($argument) { 35 | if (!$this->globalSiteSelector->isSlave()) { 36 | return; 37 | } 38 | 39 | $this->slave->batchUpdate(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/Command/UsersUpdate.php: -------------------------------------------------------------------------------- 1 | slave = $slave; 24 | } 25 | 26 | 27 | /** 28 | * 29 | */ 30 | protected function configure() { 31 | parent::configure(); 32 | $this->setName('globalsiteselector:users:update') 33 | ->setDescription('update known users data to Lookup Server'); 34 | } 35 | 36 | 37 | /** 38 | * @param InputInterface $input 39 | * @param OutputInterface $output 40 | * 41 | * @return int 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output): int { 44 | $this->slave->batchUpdate(); 45 | 46 | return 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/Controller/MasterController.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 50 | $this->session = $session; 51 | $this->gss = $globalSiteSelector; 52 | $this->master = $master; 53 | $this->logger = $logger; 54 | } 55 | 56 | /** 57 | * @PublicPage 58 | * @NoCSRFRequired 59 | * @UseSession 60 | * 61 | * @param string|null $jwt 62 | * 63 | * @return RedirectResponse 64 | */ 65 | public function autoLogout(?string $jwt) { 66 | try { 67 | if ($jwt !== null) { 68 | $key = $this->gss->getJwtKey(); 69 | $decoded = (array)JWT::decode($jwt, new Key($key, Application::JWT_ALGORITHM)); 70 | 71 | // saml idp ID 72 | $samlIdp = $decoded['saml.idp'] ?? null; 73 | // oidc provider ID 74 | $oidcProviderId = $decoded['oidc.providerId'] ?? ''; 75 | 76 | if (class_exists('\OCA\User_SAML\UserBackend')) { 77 | $logoutUrl = $this->urlGenerator->linkToRoute('user_saml.SAML.singleLogoutService'); 78 | } elseif (class_exists('\OCA\UserOIDC\User\Backend')) { 79 | $logoutUrl = $this->urlGenerator->linkToRoute('user_oidc.login.singleLogoutService'); 80 | } 81 | if (!empty($logoutUrl)) { 82 | $token = [ 83 | 'logout' => 'logout', 84 | 'idp' => $samlIdp, 85 | 'oidcProviderId' => $oidcProviderId, 86 | 'exp' => time() + 300, // expires after 5 minutes 87 | ]; 88 | 89 | $jwt = JWT::encode($token, $this->gss->getJwtKey(), Application::JWT_ALGORITHM); 90 | 91 | return new RedirectResponse($logoutUrl . '?jwt=' . $jwt); 92 | } 93 | } 94 | } catch (\Exception $e) { 95 | $this->logger->warning('remote logout request failed', ['exception' => $e]); 96 | } 97 | 98 | $home = $this->urlGenerator->getAbsoluteURL('/'); 99 | 100 | return new RedirectResponse($home); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/Exceptions/ConfigurationException.php: -------------------------------------------------------------------------------- 1 | config = $config; 37 | } 38 | 39 | /** 40 | * the global site selector can operate as 'master' or 'slave' 41 | * 42 | * @return string 43 | */ 44 | public function getMode(): string { 45 | return strtolower($this->config->getSystemValueString('gss.mode', self::SLAVE)); 46 | } 47 | 48 | /** 49 | * @return bool 50 | */ 51 | public function isMaster(): bool { 52 | return ($this->getMode() === self::MASTER); 53 | } 54 | 55 | /** 56 | * @return bool 57 | */ 58 | public function isSlave(): bool { 59 | return ($this->getMode() === self::SLAVE); 60 | } 61 | 62 | 63 | /** 64 | * get JWT key 65 | * 66 | * @return string 67 | */ 68 | public function getJwtKey(): string { 69 | // TODO: returns exception if non-existant 70 | return $this->config->getSystemValueString('gss.jwt.key', ''); 71 | } 72 | 73 | 74 | /** 75 | * get the URL of the global site selector master 76 | * 77 | * @return string 78 | * @throws MasterUrlException 79 | */ 80 | public function getMasterUrl(): string { 81 | $masterUrl = $this->config->getSystemValueString('gss.master.url', ''); 82 | if ($masterUrl === '') { 83 | throw new MasterUrlException('missing gss.master.url in config'); 84 | } 85 | 86 | return $masterUrl; 87 | } 88 | 89 | 90 | /** 91 | * get lookup server URL 92 | * 93 | * @return string 94 | */ 95 | public function getLookupServerUrl(): string { 96 | // TODO: returns exception if non-existant 97 | return $this->config->getSystemValueString('lookup_server', ''); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/Listeners/AddContentSecurityPolicyListener.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class AddContentSecurityPolicyListener implements IEventListener { 24 | 25 | public function __construct( 26 | private IConfig $config, 27 | private IUserSession $userSession, 28 | private IRequest $request, 29 | ) { 30 | } 31 | 32 | public function handle(Event $event): void { 33 | if (!$event instanceof AddContentSecurityPolicyEvent) { 34 | return; 35 | } 36 | 37 | $gssMode = $this->config->getSystemValueString('gss.mode', ''); 38 | $cspAllowList = $this->config->getSystemValue('gss.master.csp-allow', []); 39 | if ($gssMode !== 'master') { 40 | return; 41 | } 42 | if (!$this->userSession->isLoggedIn() && $this->request->getPathInfo() === '/login') { 43 | $policy = new ContentSecurityPolicy(); 44 | foreach ($cspAllowList as $entry) { 45 | $policy->addAllowedFormActionDomain($entry); 46 | } 47 | $policy->addAllowedFormActionDomain('nc://*'); 48 | $event->addPolicy($policy); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/Listeners/DeletingUser.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class DeletingUser implements IEventListener { 22 | 23 | public function __construct( 24 | private GlobalSiteSelector $globalSiteSelector, 25 | private Slave $slave, 26 | ) { 27 | } 28 | 29 | /** 30 | * @param Event $event 31 | */ 32 | public function handle(Event $event): void { 33 | if (!$event instanceof BeforeUserDeletedEvent) { 34 | return; 35 | } 36 | 37 | /** only used in slave mode */ 38 | if ($this->globalSiteSelector->getMode() !== GlobalSiteSelector::SLAVE) { 39 | return; 40 | } 41 | 42 | $params = [ 43 | 'run' => true, 44 | 'uid' => $event->getUser()->getUID() 45 | ]; 46 | 47 | $this->slave->preDeleteUser($params); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/Listeners/UserCreated.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class UserCreated implements IEventListener { 22 | 23 | public function __construct( 24 | private GlobalSiteSelector $globalSiteSelector, 25 | private Slave $slave, 26 | ) { 27 | } 28 | 29 | 30 | /** 31 | * @param Event $event 32 | */ 33 | public function handle(Event $event): void { 34 | if (!$event instanceof UserCreatedEvent) { 35 | return; 36 | } 37 | 38 | /** only used in slave mode */ 39 | if (!$this->globalSiteSelector->isSlave()) { 40 | return; 41 | } 42 | 43 | $params = [ 44 | 'uid' => $event->getUid(), 45 | 'password' => $event->getPassword() 46 | ]; 47 | 48 | $this->slave->createUser($params); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/Listeners/UserDeleted.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class UserDeleted implements IEventListener { 22 | 23 | public function __construct( 24 | private GlobalSiteSelector $globalSiteSelector, 25 | private Slave $slave, 26 | ) { 27 | } 28 | 29 | 30 | /** 31 | * @param Event $event 32 | */ 33 | public function handle(Event $event): void { 34 | if (!$event instanceof UserDeletedEvent) { 35 | return; 36 | } 37 | 38 | /** only used in slave mode */ 39 | if ($this->globalSiteSelector->getMode() !== GlobalSiteSelector::SLAVE) { 40 | return; 41 | } 42 | 43 | $params = [ 44 | 'uid' => $event->getUser()->getUID() 45 | ]; 46 | 47 | $this->slave->deleteUser($params); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/Listeners/UserLoggedOut.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class UserLoggedOut implements IEventListener { 22 | 23 | public function __construct( 24 | private GlobalSiteSelector $globalSiteSelector, 25 | private Slave $slave, 26 | ) { 27 | } 28 | 29 | /** 30 | * @param Event $event 31 | */ 32 | public function handle(Event $event): void { 33 | if (!$event instanceof UserLoggedOutEvent) { 34 | return; 35 | } 36 | 37 | $user = $event->getUser(); 38 | 39 | /** only used in slave mode */ 40 | if ($user === null || $this->globalSiteSelector->getMode() !== GlobalSiteSelector::SLAVE) { 41 | return; 42 | } 43 | 44 | $this->slave->handleLogoutRequest($user); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/Listeners/UserLoggingIn.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class UserLoggingIn implements IEventListener { 23 | 24 | public function __construct( 25 | private GlobalSiteSelector $globalSiteSelector, 26 | private Master $master, 27 | private LoggerInterface $logger, 28 | ) { 29 | } 30 | 31 | /** 32 | * @param Event $event 33 | */ 34 | public function handle(Event $event): void { 35 | if (!$event instanceof BeforeUserLoggedInEvent) { 36 | return; 37 | } 38 | 39 | /** only used in master mode */ 40 | if (!$this->globalSiteSelector->isMaster()) { 41 | return; 42 | } 43 | 44 | $this->logger->debug('new BeforeUserLoggedInEvent event'); 45 | $this->master->handleLoginRequest( 46 | $event->getUsername(), 47 | $event->getPassword(), 48 | $event->getBackend() 49 | ); 50 | 51 | $this->logger->debug('ending BeforeUserLoggedInEvent event'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/Migration/Version0110Date20180925143400.php: -------------------------------------------------------------------------------- 1 | hasTable('global_scale_users')) { 28 | $table = $schema->createTable('global_scale_users'); 29 | $table->addColumn('id', Types::BIGINT, [ 30 | 'autoincrement' => true, 31 | 'notnull' => true, 32 | 'length' => 64, 33 | ]); 34 | $table->addColumn('uid', Types::STRING, [ 35 | 'autoincrement' => false, 36 | 'notnull' => true, 37 | 'length' => 64, 38 | ]); 39 | $table->addColumn('displayname', Types::STRING, [ 40 | 'notnull' => false, 41 | 'length' => 255, 42 | 'default' => '', 43 | ]); 44 | 45 | $table->setPrimaryKey(['id']); 46 | $table->addIndex(['uid'], 'gss_uid_index'); 47 | } 48 | 49 | return $schema; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/PublicCapabilities.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'enabled' => true, 18 | 'desktoplogin' => 1, 19 | ] 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/TokenHandler.php: -------------------------------------------------------------------------------- 1 | random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); 39 | $deviceToken = $this->tokenProvider->generateToken($token, $uid, $uid, null, 'Client login', IToken::PERMANENT_TOKEN); 40 | $tokenData = $deviceToken->jsonSerialize(); 41 | 42 | return [ 43 | 'token' => $token, 44 | 'deviceToken' => $tokenData, 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/UserDiscoveryModules/IUserDiscoveryModule.php: -------------------------------------------------------------------------------- 1 | '/path/to/json-file' 22 | * 'gss.discovery.manual.mapping.parameter' => 'idp-parameter' 23 | * 24 | * And then there is another optional parameter if you want to use regular expressions: 25 | * 26 | * 'gss.discovery.manual.mapping.regex' => true 27 | * 28 | * @package OCA\GlobalSiteSelector\UserDiscoveryModules 29 | */ 30 | class ManualUserMapping implements IUserDiscoveryModule { 31 | private string $idpParameter; 32 | private string $file; 33 | private bool $useRegularExpressions; 34 | 35 | public function __construct( 36 | IConfig $config, 37 | private LoggerInterface $logger, 38 | ) { 39 | $this->idpParameter = $config->getSystemValueString('gss.discovery.manual.mapping.parameter', ''); 40 | $this->file = $config->getSystemValueString('gss.discovery.manual.mapping.file', ''); 41 | $this->useRegularExpressions = $config->getSystemValueBool('gss.discovery.manual.mapping.regex', false); 42 | 43 | $this->logger->debug('Init ManualUserMapping'); 44 | $this->logger->debug('IdP Parameter: ' . $this->idpParameter); 45 | $this->logger->debug('file: ' . $this->file); 46 | $this->logger->debug('use regular expression: ' . ($this->useRegularExpressions ? 'true' : 'false')); 47 | } 48 | 49 | 50 | /** 51 | * get the initial user location 52 | * 53 | * @param array $data idp parameters 54 | * 55 | * @return string 56 | */ 57 | public function getLocation(array $data): string { 58 | $location = ''; 59 | $dictionary = $this->getDictionary(); 60 | 61 | $key = $this->getKey($data['saml'] ?? $data['oidc']); 62 | $this->logger->debug('Lookup key is: "' . $key . '"'); 63 | 64 | // regular lookup 65 | if (!empty($key) && is_array($dictionary) && !$this->useRegularExpressions) { 66 | $location = $dictionary[$key] ?? ''; 67 | } 68 | 69 | // dictionary contains regular expressions 70 | if (!empty($key) && is_array($dictionary) && $this->useRegularExpressions) { 71 | foreach ($dictionary as $regex => $nextcloudNode) { 72 | $this->logger->debug('Testing regex: "' . $regex . '"'); 73 | if (preg_match($regex, $key) === 1) { 74 | $this->logger->debug('Regex matched'); 75 | $location = $nextcloudNode; 76 | break; 77 | } 78 | $this->logger->debug('Regex did not match'); 79 | } 80 | } 81 | 82 | $this->logger->debug('Location is: "' . $location . '"'); 83 | 84 | return $location; 85 | } 86 | 87 | /** 88 | * get dictionary which maps idp parameters to nextcloud nodes 89 | * 90 | * @return array 91 | */ 92 | private function getDictionary() { 93 | $dictionary = []; 94 | $isValidFile = !empty($this->file) && file_exists($this->file); 95 | if ($isValidFile) { 96 | $mapString = file_get_contents($this->file); 97 | $dictionary = json_decode($mapString, true); 98 | 99 | if ($dictionary === null || !is_array($dictionary)) { 100 | $this->logger->critical('Your json file at "' . $this->file . '" is not valid!'); 101 | } 102 | } 103 | 104 | return is_array($dictionary) ? $dictionary : []; 105 | } 106 | 107 | /** 108 | * get key from IDP parameter 109 | * 110 | * @param array $data idp parameters 111 | * 112 | * @return string 113 | */ 114 | private function getKey($data) { 115 | $key = ''; 116 | if (!empty($this->idpParameter) && array_key_exists($this->idpParameter, $data)) { 117 | $keys = $data[$this->idpParameter]; 118 | if (!is_array($keys)) { 119 | $keys = [$keys]; 120 | } 121 | $key = $keys[0]; 122 | $this->logger->debug('Found idpPrameter ' . $this->idpParameter . ' with value "' . $key . '"'); 123 | } else { 124 | $this->logger->debug('Could not find idpParamter: ' . $this->idpParameter); 125 | } 126 | 127 | return $this->normalizeKey($key); 128 | } 129 | 130 | /** 131 | * the keys are build like email addresses, we only need the "domain part" 132 | * 133 | * @param $key 134 | * 135 | * @return string 136 | */ 137 | private function normalizeKey($key) { 138 | $normalized = $key; 139 | $pos = strrpos($key, '@'); 140 | if ($pos !== false) { 141 | $normalized = substr($key, $pos + 1); 142 | } 143 | 144 | $this->logger->debug('Normalized key: ' . $normalized); 145 | 146 | return $normalized; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/UserDiscoveryModules/RemoteUserMapping.php: -------------------------------------------------------------------------------- 1 | '\\OCA\\GlobalSiteSelector\\UserDiscoveryModules\\RemoteUserMapping', 24 | * 'gss.discovery.remote.endpoint' => 'https://example.net/discovery.php', 25 | * 'gss.discovery.remote.secret' => 'myVeryOwnLittleSecret', 26 | */ 27 | class RemoteUserMapping implements IUserDiscoveryModule { 28 | private string $discoveryEndpoint; 29 | private string $discoverySecretKey; 30 | 31 | public function __construct( 32 | private IClientService $clientService, 33 | IConfig $config, 34 | private LoggerInterface $logger, 35 | ) { 36 | $this->discoveryEndpoint = $config->getSystemValueString('gss.discovery.remote.endpoint', ''); 37 | $this->discoverySecretKey = $config->getSystemValueString('gss.discovery.remote.secret', ''); 38 | 39 | $this->logger->debug('Init RemoteUserMapping'); 40 | $this->logger->debug('host: ' . $this->discoveryEndpoint); 41 | } 42 | 43 | public function getLocation(array $data): string { 44 | $client = $this->clientService->newClient(); 45 | if ($this->discoverySecretKey !== '') { 46 | $data['gsSecretKey'] = $this->discoverySecretKey; 47 | } 48 | 49 | try { 50 | $result = $client->post($this->discoveryEndpoint, ['body' => $data]); 51 | } catch (\Exception $e) { 52 | $this->logger->warning('cannot access remote discovery endpoint ' . $this->discoveryEndpoint, ['exception' => $e]); 53 | } 54 | 55 | try { 56 | $location = json_decode($result->getBody(), true, flags: JSON_THROW_ON_ERROR); 57 | } catch (\JsonException) { 58 | $this->logger->warning('cannot parse remote discovery endpoint result', ['result' => $result->getBody()]); 59 | } 60 | 61 | $this->logger->debug('extracted location from remote discovery: ' . json_encode($location)); 62 | return $location['location'] ?? ''; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/UserDiscoveryModules/UserDiscoveryOIDC.php: -------------------------------------------------------------------------------- 1 | 'token-attribute' 22 | * 'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoveryOIDC' 23 | * 24 | * @package OCA\GlobalSiteSelector\UserDiscoveryModule 25 | */ 26 | class UserDiscoveryOIDC implements IUserDiscoveryModule { 27 | private string $tokenLocationAttribute; 28 | 29 | public function __construct(IConfig $config) { 30 | $this->tokenLocationAttribute = $config->getSystemValueString('gss.discovery.oidc.slave.mapping', ''); 31 | } 32 | 33 | 34 | /** 35 | * read user location from OIDC token attribute 36 | * 37 | * @param array $data OIDC attributes to read the location from 38 | * 39 | * @return string 40 | */ 41 | public function getLocation(array $data): string { 42 | $location = ''; 43 | if (!empty($this->tokenLocationAttribute) && isset($data['oidc'][$this->tokenLocationAttribute])) { 44 | $location = $data['oidc'][$this->tokenLocationAttribute]; 45 | } 46 | 47 | return $location; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/UserDiscoveryModules/UserDiscoverySAML.php: -------------------------------------------------------------------------------- 1 | 'idp-parameter' 22 | * 23 | * @package OCA\GlobalSiteSelector\UserDiscoveryModule 24 | */ 25 | class UserDiscoverySAML implements IUserDiscoveryModule { 26 | private string $idpParameter; 27 | 28 | public function __construct(IConfig $config) { 29 | $this->idpParameter = $config->getSystemValueString('gss.discovery.saml.slave.mapping', ''); 30 | } 31 | 32 | 33 | /** 34 | * read user location from SAML parameters 35 | * 36 | * @param array $data SAML Parameters to read the location from 37 | * 38 | * @return string 39 | */ 40 | public function getLocation(array $data): string { 41 | $location = ''; 42 | if (!empty($this->idpParameter) && isset($data['saml'][$this->idpParameter][0])) { 43 | $location = $data['saml'][$this->idpParameter][0]; 44 | } 45 | 46 | return $location; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/Vendor/Firebase/JWT/BeforeValidException.php: -------------------------------------------------------------------------------- 1 | payload = $payload; 12 | } 13 | 14 | public function getPayload(): object 15 | { 16 | return $this->payload; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/Vendor/Firebase/JWT/ExpiredException.php: -------------------------------------------------------------------------------- 1 | payload = $payload; 12 | } 13 | 14 | public function getPayload(): object 15 | { 16 | return $this->payload; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/Vendor/Firebase/JWT/JWTExceptionWithPayloadInterface.php: -------------------------------------------------------------------------------- 1 | algorithm; 46 | } 47 | 48 | /** 49 | * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate 50 | */ 51 | public function getKeyMaterial() 52 | { 53 | return $this->keyMaterial; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/Vendor/Firebase/JWT/SignatureInvalidException.php: -------------------------------------------------------------------------------- 1 | loadApp('globalsiteselector'); 26 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 13 | 14 | . 15 | ./stubs/ 16 | 17 | 18 | 19 | 20 | 21 | ../../../globalsiteselector 22 | 23 | ../../../globalsiteselector/l10n 24 | ../../../globalsiteselector/lists 25 | ../../../globalsiteselector/tests/unit 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/psalm-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | \OCA\User_SAML\UserBackend 10 | 11 | 12 | 13 | 14 | \OC\User\Database 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/stubs/doctrine_dbal_schema_abstractasset.php: -------------------------------------------------------------------------------- 1 | Table($tableName)); if you want to rename the table, you have to make sure this does not get 24 | * recreated during schema migration. 25 | */ 26 | abstract class AbstractAsset 27 | { 28 | /** @var string */ 29 | protected $_name = ''; 30 | 31 | /** 32 | * Namespace of the asset. If none isset the default namespace is assumed. 33 | * 34 | * @var string|null 35 | */ 36 | protected $_namespace; 37 | 38 | /** @var bool */ 39 | protected $_quoted = false; 40 | 41 | /** 42 | * Sets the name of this asset. 43 | * 44 | * @param string $name 45 | * 46 | * @return void 47 | */ 48 | protected function _setName($name) 49 | { 50 | } 51 | 52 | /** 53 | * Is this asset in the default namespace? 54 | * 55 | * @param string $defaultNamespaceName 56 | * 57 | * @return bool 58 | */ 59 | public function isInDefaultNamespace($defaultNamespaceName) 60 | { 61 | } 62 | 63 | /** 64 | * Gets the namespace name of this asset. 65 | * 66 | * If NULL is returned this means the default namespace is used. 67 | * 68 | * @return string|null 69 | */ 70 | public function getNamespaceName() 71 | { 72 | } 73 | 74 | /** 75 | * The shortest name is stripped of the default namespace. All other 76 | * namespaced elements are returned as full-qualified names. 77 | * 78 | * @param string|null $defaultNamespaceName 79 | * 80 | * @return string 81 | */ 82 | public function getShortestName($defaultNamespaceName) 83 | { 84 | } 85 | 86 | /** 87 | * The normalized name is full-qualified and lower-cased. Lower-casing is 88 | * actually wrong, but we have to do it to keep our sanity. If you are 89 | * using database objects that only differentiate in the casing (FOO vs 90 | * Foo) then you will NOT be able to use Doctrine Schema abstraction. 91 | * 92 | * Every non-namespaced element is prefixed with the default namespace 93 | * name which is passed as argument to this method. 94 | * 95 | * @deprecated Use {@see getNamespaceName()} and {@see getName()} instead. 96 | * 97 | * @param string $defaultNamespaceName 98 | * 99 | * @return string 100 | */ 101 | public function getFullQualifiedName($defaultNamespaceName) 102 | { 103 | } 104 | 105 | /** 106 | * Checks if this asset's name is quoted. 107 | * 108 | * @return bool 109 | */ 110 | public function isQuoted() 111 | { 112 | } 113 | 114 | /** 115 | * Checks if this identifier is quoted. 116 | * 117 | * @param string $identifier 118 | * 119 | * @return bool 120 | */ 121 | protected function isIdentifierQuoted($identifier) 122 | { 123 | } 124 | 125 | /** 126 | * Trim quotes from the identifier. 127 | * 128 | * @param string $identifier 129 | * 130 | * @return string 131 | */ 132 | protected function trimQuotes($identifier) 133 | { 134 | } 135 | 136 | /** 137 | * Returns the name of this schema asset. 138 | * 139 | * @return string 140 | */ 141 | public function getName() 142 | { 143 | } 144 | 145 | /** 146 | * Gets the quoted representation of this asset but only if it was defined with one. Otherwise 147 | * return the plain unquoted value as inserted. 148 | * 149 | * @return string 150 | */ 151 | public function getQuotedName(AbstractPlatform $platform) 152 | { 153 | } 154 | 155 | /** 156 | * Generates an identifier from a list of column names obeying a certain string length. 157 | * 158 | * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, 159 | * however building idents automatically for foreign keys, composite keys or such can easily create 160 | * very long names. 161 | * 162 | * @param string[] $columnNames 163 | * @param string $prefix 164 | * @param int $maxSize 165 | * 166 | * @return string 167 | */ 168 | protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30) 169 | { 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /tests/stubs/icewind_streams_directory.php: -------------------------------------------------------------------------------- 1 | 4 | * This file is licensed under the Licensed under the MIT license: 5 | * http://opensource.org/licenses/MIT 6 | */ 7 | 8 | namespace Icewind\Streams; 9 | 10 | /** 11 | * Interface for stream wrappers that implements a directory 12 | */ 13 | interface Directory { 14 | /** 15 | * @param string $path 16 | * @param array $options 17 | * @return bool 18 | */ 19 | public function dir_opendir($path, $options) 20 | { 21 | } 22 | 23 | /** 24 | * @return string|bool 25 | */ 26 | public function dir_readdir() 27 | { 28 | } 29 | 30 | /** 31 | * @return bool 32 | */ 33 | public function dir_closedir() 34 | { 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function dir_rewinddir() 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/stubs/icewind_streams_iteratordirectory.php: -------------------------------------------------------------------------------- 1 | 4 | * This file is licensed under the Licensed under the MIT license: 5 | * http://opensource.org/licenses/MIT 6 | */ 7 | 8 | namespace Icewind\Streams; 9 | 10 | /** 11 | * Create a directory handle from an iterator or array 12 | * 13 | * The following options should be passed in the context when opening the stream 14 | * [ 15 | * 'dir' => [ 16 | * 'array' => string[] 17 | * 'iterator' => \Iterator 18 | * ] 19 | * ] 20 | * 21 | * Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference 22 | */ 23 | class IteratorDirectory extends WrapperHandler implements Directory { 24 | /** 25 | * @var resource 26 | */ 27 | public $context; 28 | 29 | /** 30 | * @var \Iterator 31 | */ 32 | protected $iterator; 33 | 34 | /** 35 | * Load the source from the stream context and return the context options 36 | * 37 | * @param string $name 38 | * @return array 39 | * @throws \BadMethodCallException 40 | */ 41 | protected function loadContext($name = null) 42 | { 43 | } 44 | 45 | /** 46 | * @param string $path 47 | * @param array $options 48 | * @return bool 49 | */ 50 | public function dir_opendir($path, $options) 51 | { 52 | } 53 | 54 | /** 55 | * @return string|bool 56 | */ 57 | public function dir_readdir() 58 | { 59 | } 60 | 61 | /** 62 | * @return bool 63 | */ 64 | public function dir_closedir() 65 | { 66 | } 67 | 68 | /** 69 | * @return bool 70 | */ 71 | public function dir_rewinddir() 72 | { 73 | } 74 | 75 | /** 76 | * Creates a directory handle from the provided array or iterator 77 | * 78 | * @param \Iterator | array $source 79 | * @return resource|false 80 | * 81 | * @throws \BadMethodCallException 82 | */ 83 | public static function wrap($source) 84 | { 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/stubs/icewind_streams_wrapperhandler.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @license GNU AGPL version 3 or any later version 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | * 20 | */ 21 | 22 | namespace Icewind\Streams; 23 | 24 | class WrapperHandler { 25 | /** @var resource $context */ 26 | protected $context; 27 | 28 | const NO_SOURCE_DIR = 1; 29 | 30 | /** 31 | * get the protocol name that is generated for the class 32 | * @param string|null $class 33 | * @return string 34 | */ 35 | public static function getProtocol($class = null) 36 | { 37 | } 38 | 39 | /** 40 | * @param resource|int $source 41 | * @param resource|array $context 42 | * @param string|null $protocol deprecated, protocol is now automatically generated 43 | * @param string|null $class deprecated, class is now automatically generated 44 | * @return resource|false 45 | */ 46 | protected static function wrapSource($source, $context = [], $protocol = null, $class = null, $mode = 'r+') 47 | { 48 | } 49 | 50 | protected static function isDirectoryHandle($resource) 51 | { 52 | } 53 | 54 | /** 55 | * Load the source from the stream context and return the context options 56 | * 57 | * @param string|null $name if not set, the generated protocol name is used 58 | * @return array 59 | * @throws \BadMethodCallException 60 | */ 61 | protected function loadContext($name = null) 62 | { 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/stubs/oc.php: -------------------------------------------------------------------------------- 1 | filename 33 | */ 34 | public static array $CLASSPATH = []; 35 | /** 36 | * The installation path for Nextcloud on the server (e.g. /srv/http/nextcloud) 37 | */ 38 | public static string $SERVERROOT = ''; 39 | /** 40 | * the Nextcloud root path for http requests (e.g. /nextcloud) 41 | */ 42 | public static string $WEBROOT = ''; 43 | /** 44 | * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and 45 | * web path in 'url' 46 | */ 47 | public static array $APPSROOTS = []; 48 | 49 | public static string $configDir; 50 | 51 | /** 52 | * requested app 53 | */ 54 | public static string $REQUESTEDAPP = ''; 55 | 56 | /** 57 | * check if Nextcloud runs in cli mode 58 | */ 59 | public static bool $CLI = false; 60 | 61 | public static \OC\Autoloader $loader; 62 | 63 | public static \Composer\Autoload\ClassLoader $composerAutoloader; 64 | 65 | public static \OC\Server $server; 66 | 67 | /** 68 | * @throws \RuntimeException when the 3rdparty directory is missing or 69 | * the app path list is empty or contains an invalid path 70 | */ 71 | public static function initPaths(): void 72 | { 73 | } 74 | 75 | public static function checkConfig(): void 76 | { 77 | } 78 | 79 | public static function checkInstalled(\OC\SystemConfig $systemConfig): void 80 | { 81 | } 82 | 83 | public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void 84 | { 85 | } 86 | 87 | public static function initSession(): void 88 | { 89 | } 90 | 91 | /** 92 | * @return bool true if the session expiry should only be done by gc instead of an explicit timeout 93 | */ 94 | public static function hasSessionRelaxedExpiry(): bool 95 | { 96 | } 97 | 98 | /** 99 | * Try to set some values to the required Nextcloud default 100 | */ 101 | public static function setRequiredIniValues(): void 102 | { 103 | } 104 | 105 | public static function init(): void 106 | { 107 | } 108 | 109 | /** 110 | * register hooks for the cleanup of cache and bruteforce protection 111 | */ 112 | public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void 113 | { 114 | } 115 | 116 | /** 117 | * register hooks for sharing 118 | */ 119 | public static function registerShareHooks(\OC\SystemConfig $systemConfig): void 120 | { 121 | } 122 | 123 | protected static function registerAutoloaderCache(\OC\SystemConfig $systemConfig): void 124 | { 125 | } 126 | 127 | /** 128 | * Handle the request 129 | */ 130 | public static function handleRequest(): void 131 | { 132 | } 133 | 134 | /** 135 | * Check login: apache auth, auth token, basic auth 136 | */ 137 | public static function handleLogin(OCP\IRequest $request): bool 138 | { 139 | } 140 | 141 | protected static function handleAuthHeaders(): void 142 | { 143 | } 144 | 145 | protected static function tryAppAPILogin(OCP\IRequest $request): bool 146 | { 147 | } 148 | } 149 | 150 | OC::init(); 151 | -------------------------------------------------------------------------------- /tests/stubs/oc_appframework_ocs_baseresponse.php: -------------------------------------------------------------------------------- 1 | 17 | * @template-extends Response> 18 | */ 19 | abstract class BaseResponse extends Response { 20 | /** @var array */ 21 | protected $data; 22 | 23 | /** @var string */ 24 | protected $format; 25 | 26 | /** @var ?string */ 27 | protected $statusMessage; 28 | 29 | /** @var ?int */ 30 | protected $itemsCount; 31 | 32 | /** @var ?int */ 33 | protected $itemsPerPage; 34 | 35 | /** 36 | * BaseResponse constructor. 37 | * 38 | * @param DataResponse $dataResponse 39 | * @param string $format 40 | * @param string|null $statusMessage 41 | * @param int|null $itemsCount 42 | * @param int|null $itemsPerPage 43 | */ 44 | public function __construct(DataResponse $dataResponse, $format = 'xml', $statusMessage = null, $itemsCount = null, $itemsPerPage = null) 45 | { 46 | } 47 | 48 | /** 49 | * @param array $meta 50 | * @return string 51 | */ 52 | protected function renderResult(array $meta): string 53 | { 54 | } 55 | 56 | protected function toXML(array $array, \XMLWriter $writer): void 57 | { 58 | } 59 | 60 | public function getOCSStatus() 61 | { 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/stubs/oc_appframework_ocs_v1response.php: -------------------------------------------------------------------------------- 1 | 17 | * @template-extends BaseResponse> 18 | */ 19 | class V1Response extends BaseResponse { 20 | /** 21 | * The V1 endpoint has very limited http status codes basically everything 22 | * is status 200 except 401 23 | * 24 | * @return int 25 | */ 26 | public function getStatus() 27 | { 28 | } 29 | 30 | /** 31 | * In v1 all OK is 100 32 | * 33 | * @return int 34 | */ 35 | public function getOCSStatus() 36 | { 37 | } 38 | 39 | /** 40 | * Construct the meta part of the response 41 | * And then late the base class render 42 | * 43 | * @return string 44 | */ 45 | public function render() 46 | { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/stubs/oc_appframework_utility_simplecontainer.php: -------------------------------------------------------------------------------- 1 | |string $id 33 | * @return T|mixed 34 | * @psalm-template S as class-string|string 35 | * @psalm-param S $id 36 | * @psalm-return (S is class-string ? T : mixed) 37 | */ 38 | public function get(string $id): mixed 39 | { 40 | } 41 | 42 | public function has(string $id): bool 43 | { 44 | } 45 | 46 | public function resolve($name) 47 | { 48 | } 49 | 50 | public function query(string $name, bool $autoload = true) 51 | { 52 | } 53 | 54 | /** 55 | * @param string $name 56 | * @param mixed $value 57 | */ 58 | public function registerParameter($name, $value) 59 | { 60 | } 61 | 62 | /** 63 | * The given closure is call the first time the given service is queried. 64 | * The closure has to return the instance for the given service. 65 | * Created instance will be cached in case $shared is true. 66 | * 67 | * @param string $name name of the service to register another backend for 68 | * @param Closure $closure the closure to be called on service creation 69 | * @param bool $shared 70 | */ 71 | public function registerService($name, Closure $closure, $shared = true) 72 | { 73 | } 74 | 75 | /** 76 | * Shortcut for returning a service from a service under a different key, 77 | * e.g. to tell the container to return a class when queried for an 78 | * interface 79 | * @param string $alias the alias that should be registered 80 | * @param string $target the target that should be resolved instead 81 | */ 82 | public function registerAlias($alias, $target) 83 | { 84 | } 85 | 86 | /* 87 | * @param string $name 88 | * @return string 89 | */ 90 | protected function sanitizeName($name) 91 | { 92 | } 93 | 94 | /** 95 | * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::has 96 | */ 97 | public function offsetExists($id): bool 98 | { 99 | } 100 | 101 | /** 102 | * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get 103 | * @return mixed 104 | */ 105 | #[\ReturnTypeWillChange] 106 | public function offsetGet($id) 107 | { 108 | } 109 | 110 | /** 111 | * @deprecated 20.0.0 use \OCP\IContainer::registerService 112 | */ 113 | public function offsetSet($offset, $value): void 114 | { 115 | } 116 | 117 | /** 118 | * @deprecated 20.0.0 119 | */ 120 | public function offsetUnset($offset): void 121 | { 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/stubs/oc_core_command_base.php: -------------------------------------------------------------------------------- 1 | $storage, 'root' => $root] 34 | * 35 | * $storage: The storage that will be wrapper 36 | * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage 37 | */ 38 | public function __construct($arguments) 39 | { 40 | } 41 | 42 | public function getUnjailedPath(string $path): string 43 | { 44 | } 45 | 46 | /** 47 | * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper 48 | */ 49 | public function getUnjailedStorage(): IStorage 50 | { 51 | } 52 | 53 | 54 | public function getJailedPath(string $path): ?string 55 | { 56 | } 57 | 58 | public function getId(): string 59 | { 60 | } 61 | 62 | public function mkdir(string $path): bool 63 | { 64 | } 65 | 66 | public function rmdir(string $path): bool 67 | { 68 | } 69 | 70 | public function opendir(string $path) 71 | { 72 | } 73 | 74 | public function is_dir(string $path): bool 75 | { 76 | } 77 | 78 | public function is_file(string $path): bool 79 | { 80 | } 81 | 82 | public function stat(string $path): array|false 83 | { 84 | } 85 | 86 | public function filetype(string $path): string|false 87 | { 88 | } 89 | 90 | public function filesize(string $path): int|float|false 91 | { 92 | } 93 | 94 | public function isCreatable(string $path): bool 95 | { 96 | } 97 | 98 | public function isReadable(string $path): bool 99 | { 100 | } 101 | 102 | public function isUpdatable(string $path): bool 103 | { 104 | } 105 | 106 | public function isDeletable(string $path): bool 107 | { 108 | } 109 | 110 | public function isSharable(string $path): bool 111 | { 112 | } 113 | 114 | public function getPermissions(string $path): int 115 | { 116 | } 117 | 118 | public function file_exists(string $path): bool 119 | { 120 | } 121 | 122 | public function filemtime(string $path): int|false 123 | { 124 | } 125 | 126 | public function file_get_contents(string $path): string|false 127 | { 128 | } 129 | 130 | public function file_put_contents(string $path, mixed $data): int|float|false 131 | { 132 | } 133 | 134 | public function unlink(string $path): bool 135 | { 136 | } 137 | 138 | public function rename(string $source, string $target): bool 139 | { 140 | } 141 | 142 | public function copy(string $source, string $target): bool 143 | { 144 | } 145 | 146 | public function fopen(string $path, string $mode) 147 | { 148 | } 149 | 150 | public function getMimeType(string $path): string|false 151 | { 152 | } 153 | 154 | public function hash(string $type, string $path, bool $raw = false): string|false 155 | { 156 | } 157 | 158 | public function free_space(string $path): int|float|false 159 | { 160 | } 161 | 162 | public function touch(string $path, ?int $mtime = null): bool 163 | { 164 | } 165 | 166 | public function getLocalFile(string $path): string|false 167 | { 168 | } 169 | 170 | public function hasUpdated(string $path, int $time): bool 171 | { 172 | } 173 | 174 | public function getCache(string $path = '', ?IStorage $storage = null): ICache 175 | { 176 | } 177 | 178 | public function getOwner(string $path): string|false 179 | { 180 | } 181 | 182 | public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher 183 | { 184 | } 185 | 186 | public function getETag(string $path): string|false 187 | { 188 | } 189 | 190 | public function getMetaData(string $path): ?array 191 | { 192 | } 193 | 194 | public function acquireLock(string $path, int $type, ILockingProvider $provider): void 195 | { 196 | } 197 | 198 | public function releaseLock(string $path, int $type, ILockingProvider $provider): void 199 | { 200 | } 201 | 202 | public function changeLock(string $path, int $type, ILockingProvider $provider): void 203 | { 204 | } 205 | 206 | /** 207 | * Resolve the path for the source of the share 208 | */ 209 | public function resolvePath(string $path): array 210 | { 211 | } 212 | 213 | public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool 214 | { 215 | } 216 | 217 | public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool 218 | { 219 | } 220 | 221 | public function getPropagator(?IStorage $storage = null): IPropagator 222 | { 223 | } 224 | 225 | public function writeStream(string $path, $stream, ?int $size = null): int 226 | { 227 | } 228 | 229 | public function getDirectoryContent(string $directory): \Traversable 230 | { 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /tests/stubs/oc_files_storage_wrapper_permissionsmask.php: -------------------------------------------------------------------------------- 1 | $storage, 'mask' => $mask] 24 | * 25 | * $storage: The storage the permissions mask should be applied on 26 | * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants 27 | */ 28 | public function __construct($arguments) 29 | { 30 | } 31 | 32 | public function isUpdatable(string $path): bool 33 | { 34 | } 35 | 36 | public function isCreatable(string $path): bool 37 | { 38 | } 39 | 40 | public function isDeletable(string $path): bool 41 | { 42 | } 43 | 44 | public function isSharable(string $path): bool 45 | { 46 | } 47 | 48 | public function getPermissions(string $path): int 49 | { 50 | } 51 | 52 | public function rename(string $source, string $target): bool 53 | { 54 | } 55 | 56 | public function copy(string $source, string $target): bool 57 | { 58 | } 59 | 60 | public function touch(string $path, ?int $mtime = null): bool 61 | { 62 | } 63 | 64 | public function mkdir(string $path): bool 65 | { 66 | } 67 | 68 | public function rmdir(string $path): bool 69 | { 70 | } 71 | 72 | public function unlink(string $path): bool 73 | { 74 | } 75 | 76 | public function file_put_contents(string $path, mixed $data): int|float|false 77 | { 78 | } 79 | 80 | public function fopen(string $path, string $mode) 81 | { 82 | } 83 | 84 | public function getCache(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\ICache 85 | { 86 | } 87 | 88 | public function getMetaData(string $path): ?array 89 | { 90 | } 91 | 92 | public function getScanner(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\IScanner 93 | { 94 | } 95 | 96 | public function getDirectoryContent(string $directory): \Traversable 97 | { 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/stubs/oc_files_storage_wrapper_quota.php: -------------------------------------------------------------------------------- 1 | |string $name 82 | * @return T|mixed 83 | * @psalm-template S as class-string|string 84 | * @psalm-param S $name 85 | * @psalm-return (S is class-string ? T : mixed) 86 | * @throws QueryException 87 | * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get 88 | */ 89 | public function query(string $name, bool $autoload = true) 90 | { 91 | } 92 | 93 | /** 94 | * @internal 95 | * @param string $id 96 | * @return DIContainer|null 97 | */ 98 | public function getAppContainerForService(string $id): ?DIContainer 99 | { 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/stubs/oc_settings_authorizedgroupmapper.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class AuthorizedGroupMapper extends QBMapper { 22 | public function __construct(IDBConnection $db) 23 | { 24 | } 25 | 26 | /** 27 | * @throws Exception 28 | */ 29 | public function findAllClassesForUser(IUser $user): array 30 | { 31 | } 32 | 33 | /** 34 | * @throws \OCP\AppFramework\Db\DoesNotExistException 35 | * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException 36 | * @throws \OCP\DB\Exception 37 | */ 38 | public function find(int $id): AuthorizedGroup 39 | { 40 | } 41 | 42 | /** 43 | * Get all the authorizations stored in the database. 44 | * 45 | * @return AuthorizedGroup[] 46 | * @throws \OCP\DB\Exception 47 | */ 48 | public function findAll(): array 49 | { 50 | } 51 | 52 | public function findByGroupIdAndClass(string $groupId, string $class) 53 | { 54 | } 55 | 56 | /** 57 | * @return Entity[] 58 | * @throws \OCP\DB\Exception 59 | */ 60 | public function findExistingGroupsForClass(string $class): array 61 | { 62 | } 63 | 64 | /** 65 | * @throws Exception 66 | */ 67 | public function removeGroup(string $gid) 68 | { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/stubs/oca_circles_circlesqueryhelper.php: -------------------------------------------------------------------------------- 1 | 167 | */ 168 | public function getMetadata(): array 169 | { 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /tests/stubs/oca_files_versions_expiration.php: -------------------------------------------------------------------------------- 1 | value mapping. 16 | * @since 29.0.0 17 | */ 18 | interface IMetadataVersionBackend { 19 | /** 20 | * Sets a key value pair in the metadata column corresponding to the node's version. 21 | * 22 | * @param Node $node the node that triggered the Metadata event listener, aka, the file version 23 | * @param int $revision the key for the json value of the metadata column 24 | * @param string $key the key for the json value of the metadata column 25 | * @param string $value the value that corresponds to the key in the metadata column 26 | * @since 29.0.0 27 | */ 28 | public function setMetadataValue(Node $node, int $revision, string $key, string $value): void 29 | { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/stubs/oca_files_versions_versions_ineedsyncversionbackend.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Console\Input; 13 | 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\Console\Completion\CompletionInput; 16 | use Symfony\Component\Console\Completion\CompletionSuggestions; 17 | use Symfony\Component\Console\Completion\Suggestion; 18 | use Symfony\Component\Console\Exception\InvalidArgumentException; 19 | use Symfony\Component\Console\Exception\LogicException; 20 | 21 | /** 22 | * Represents a command line argument. 23 | * 24 | * @author Fabien Potencier 25 | */ 26 | class InputArgument 27 | { 28 | public const REQUIRED = 1; 29 | public const OPTIONAL = 2; 30 | public const IS_ARRAY = 4; 31 | 32 | /** 33 | * @param string $name The argument name 34 | * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY 35 | * @param string $description A description text 36 | * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) 37 | * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion 38 | * 39 | * @throws InvalidArgumentException When argument mode is not valid 40 | */ 41 | public function __construct(string $name, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = []) 42 | { 43 | } 44 | 45 | /** 46 | * Returns the argument name. 47 | */ 48 | public function getName(): string 49 | { 50 | } 51 | 52 | /** 53 | * Returns true if the argument is required. 54 | * 55 | * @return bool true if parameter mode is self::REQUIRED, false otherwise 56 | */ 57 | public function isRequired(): bool 58 | { 59 | } 60 | 61 | /** 62 | * Returns true if the argument can take multiple values. 63 | * 64 | * @return bool true if mode is self::IS_ARRAY, false otherwise 65 | */ 66 | public function isArray(): bool 67 | { 68 | } 69 | 70 | /** 71 | * Sets the default value. 72 | * 73 | * @return void 74 | * 75 | * @throws LogicException When incorrect default value is given 76 | */ 77 | public function setDefault(string|bool|int|float|array|null $default = null) 78 | { 79 | } 80 | 81 | /** 82 | * Returns the default value. 83 | */ 84 | public function getDefault(): string|bool|int|float|array|null 85 | { 86 | } 87 | 88 | public function hasCompletion(): bool 89 | { 90 | } 91 | 92 | /** 93 | * Adds suggestions to $suggestions for the current completion input. 94 | * 95 | * @see Command::complete() 96 | */ 97 | public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void 98 | { 99 | } 100 | 101 | /** 102 | * Returns the description text. 103 | */ 104 | public function getDescription(): string 105 | { 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/stubs/symfony_component_console_input_inputoption.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Console\Input; 13 | 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\Console\Completion\CompletionInput; 16 | use Symfony\Component\Console\Completion\CompletionSuggestions; 17 | use Symfony\Component\Console\Completion\Suggestion; 18 | use Symfony\Component\Console\Exception\InvalidArgumentException; 19 | use Symfony\Component\Console\Exception\LogicException; 20 | 21 | /** 22 | * Represents a command line option. 23 | * 24 | * @author Fabien Potencier 25 | */ 26 | class InputOption 27 | { 28 | /** 29 | * Do not accept input for the option (e.g. --yell). This is the default behavior of options. 30 | */ 31 | public const VALUE_NONE = 1; 32 | 33 | /** 34 | * A value must be passed when the option is used (e.g. --iterations=5 or -i5). 35 | */ 36 | public const VALUE_REQUIRED = 2; 37 | 38 | /** 39 | * The option may or may not have a value (e.g. --yell or --yell=loud). 40 | */ 41 | public const VALUE_OPTIONAL = 4; 42 | 43 | /** 44 | * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). 45 | */ 46 | public const VALUE_IS_ARRAY = 8; 47 | 48 | /** 49 | * The option may have either positive or negative value (e.g. --ansi or --no-ansi). 50 | */ 51 | public const VALUE_NEGATABLE = 16; 52 | 53 | /** 54 | * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts 55 | * @param int|null $mode The option mode: One of the VALUE_* constants 56 | * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) 57 | * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion 58 | * 59 | * @throws InvalidArgumentException If option mode is invalid or incompatible 60 | */ 61 | public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = []) 62 | { 63 | } 64 | 65 | /** 66 | * Returns the option shortcut. 67 | */ 68 | public function getShortcut(): ?string 69 | { 70 | } 71 | 72 | /** 73 | * Returns the option name. 74 | */ 75 | public function getName(): string 76 | { 77 | } 78 | 79 | /** 80 | * Returns true if the option accepts a value. 81 | * 82 | * @return bool true if value mode is not self::VALUE_NONE, false otherwise 83 | */ 84 | public function acceptValue(): bool 85 | { 86 | } 87 | 88 | /** 89 | * Returns true if the option requires a value. 90 | * 91 | * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise 92 | */ 93 | public function isValueRequired(): bool 94 | { 95 | } 96 | 97 | /** 98 | * Returns true if the option takes an optional value. 99 | * 100 | * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise 101 | */ 102 | public function isValueOptional(): bool 103 | { 104 | } 105 | 106 | /** 107 | * Returns true if the option can take multiple values. 108 | * 109 | * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise 110 | */ 111 | public function isArray(): bool 112 | { 113 | } 114 | 115 | public function isNegatable(): bool 116 | { 117 | } 118 | 119 | /** 120 | * @return void 121 | */ 122 | public function setDefault(string|bool|int|float|array|null $default = null) 123 | { 124 | } 125 | 126 | /** 127 | * Returns the default value. 128 | */ 129 | public function getDefault(): string|bool|int|float|array|null 130 | { 131 | } 132 | 133 | /** 134 | * Returns the description text. 135 | */ 136 | public function getDescription(): string 137 | { 138 | } 139 | 140 | public function hasCompletion(): bool 141 | { 142 | } 143 | 144 | /** 145 | * Adds suggestions to $suggestions for the current completion input. 146 | * 147 | * @see Command::complete() 148 | */ 149 | public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void 150 | { 151 | } 152 | 153 | /** 154 | * Checks whether the given option equals this one. 155 | */ 156 | public function equals(self $option): bool 157 | { 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /tests/stubs/symfony_component_console_output_outputinterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Console\Output; 13 | 14 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 15 | 16 | /** 17 | * OutputInterface is the interface implemented by all Output classes. 18 | * 19 | * @author Fabien Potencier 20 | */ 21 | interface OutputInterface 22 | { 23 | public const VERBOSITY_QUIET = 16; 24 | public const VERBOSITY_NORMAL = 32; 25 | public const VERBOSITY_VERBOSE = 64; 26 | public const VERBOSITY_VERY_VERBOSE = 128; 27 | public const VERBOSITY_DEBUG = 256; 28 | 29 | public const OUTPUT_NORMAL = 1; 30 | public const OUTPUT_RAW = 2; 31 | public const OUTPUT_PLAIN = 4; 32 | 33 | /** 34 | * Writes a message to the output. 35 | * 36 | * @param bool $newline Whether to add a newline 37 | * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 38 | * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL 39 | * 40 | * @return void 41 | */ 42 | public function write(string|iterable $messages, bool $newline = false, int $options = 0) 43 | { 44 | } 45 | 46 | /** 47 | * Writes a message to the output and adds a newline at the end. 48 | * 49 | * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 50 | * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL 51 | * 52 | * @return void 53 | */ 54 | public function writeln(string|iterable $messages, int $options = 0) 55 | { 56 | } 57 | 58 | /** 59 | * Sets the verbosity of the output. 60 | * 61 | * @param self::VERBOSITY_* $level 62 | * 63 | * @return void 64 | */ 65 | public function setVerbosity(int $level) 66 | { 67 | } 68 | 69 | /** 70 | * Gets the current verbosity of the output. 71 | * 72 | * @return self::VERBOSITY_* 73 | */ 74 | public function getVerbosity(): int 75 | { 76 | } 77 | 78 | /** 79 | * Returns whether verbosity is quiet (-q). 80 | */ 81 | public function isQuiet(): bool 82 | { 83 | } 84 | 85 | /** 86 | * Returns whether verbosity is verbose (-v). 87 | */ 88 | public function isVerbose(): bool 89 | { 90 | } 91 | 92 | /** 93 | * Returns whether verbosity is very verbose (-vv). 94 | */ 95 | public function isVeryVerbose(): bool 96 | { 97 | } 98 | 99 | /** 100 | * Returns whether verbosity is debug (-vvv). 101 | */ 102 | public function isDebug(): bool 103 | { 104 | } 105 | 106 | /** 107 | * Sets the decorated flag. 108 | * 109 | * @return void 110 | */ 111 | public function setDecorated(bool $decorated) 112 | { 113 | } 114 | 115 | /** 116 | * Gets the decorated flag. 117 | */ 118 | public function isDecorated(): bool 119 | { 120 | } 121 | 122 | /** 123 | * @return void 124 | */ 125 | public function setFormatter(OutputFormatterInterface $formatter) 126 | { 127 | } 128 | 129 | /** 130 | * Returns current output formatter instance. 131 | */ 132 | public function getFormatter(): OutputFormatterInterface 133 | { 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/stubs/symfony_component_console_question_confirmationquestion.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Console\Question; 13 | 14 | /** 15 | * Represents a yes/no question. 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | class ConfirmationQuestion extends Question 20 | { 21 | /** 22 | * @param string $question The question to ask to the user 23 | * @param bool $default The default answer to return, true or false 24 | * @param string $trueAnswerRegex A regex to match the "yes" answer 25 | */ 26 | public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/stubs/symfony_component_console_question_question.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Console\Question; 13 | 14 | use Symfony\Component\Console\Exception\InvalidArgumentException; 15 | use Symfony\Component\Console\Exception\LogicException; 16 | 17 | /** 18 | * Represents a Question. 19 | * 20 | * @author Fabien Potencier 21 | */ 22 | class Question 23 | { 24 | /** 25 | * @param string $question The question to ask to the user 26 | * @param string|bool|int|float|null $default The default answer to return if the user enters nothing 27 | */ 28 | public function __construct(string $question, string|bool|int|float|null $default = null) 29 | { 30 | } 31 | 32 | /** 33 | * Returns the question. 34 | */ 35 | public function getQuestion(): string 36 | { 37 | } 38 | 39 | /** 40 | * Returns the default answer. 41 | */ 42 | public function getDefault(): string|bool|int|float|null 43 | { 44 | } 45 | 46 | /** 47 | * Returns whether the user response accepts newline characters. 48 | */ 49 | public function isMultiline(): bool 50 | { 51 | } 52 | 53 | /** 54 | * Sets whether the user response should accept newline characters. 55 | * 56 | * @return $this 57 | */ 58 | public function setMultiline(bool $multiline): static 59 | { 60 | } 61 | 62 | /** 63 | * Returns whether the user response must be hidden. 64 | */ 65 | public function isHidden(): bool 66 | { 67 | } 68 | 69 | /** 70 | * Sets whether the user response must be hidden or not. 71 | * 72 | * @return $this 73 | * 74 | * @throws LogicException In case the autocompleter is also used 75 | */ 76 | public function setHidden(bool $hidden): static 77 | { 78 | } 79 | 80 | /** 81 | * In case the response cannot be hidden, whether to fallback on non-hidden question or not. 82 | */ 83 | public function isHiddenFallback(): bool 84 | { 85 | } 86 | 87 | /** 88 | * Sets whether to fallback on non-hidden question if the response cannot be hidden. 89 | * 90 | * @return $this 91 | */ 92 | public function setHiddenFallback(bool $fallback): static 93 | { 94 | } 95 | 96 | /** 97 | * Gets values for the autocompleter. 98 | */ 99 | public function getAutocompleterValues(): ?iterable 100 | { 101 | } 102 | 103 | /** 104 | * Sets values for the autocompleter. 105 | * 106 | * @return $this 107 | * 108 | * @throws LogicException 109 | */ 110 | public function setAutocompleterValues(?iterable $values): static 111 | { 112 | } 113 | 114 | /** 115 | * Gets the callback function used for the autocompleter. 116 | */ 117 | public function getAutocompleterCallback(): ?callable 118 | { 119 | } 120 | 121 | /** 122 | * Sets the callback function used for the autocompleter. 123 | * 124 | * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. 125 | * 126 | * @return $this 127 | */ 128 | public function setAutocompleterCallback(?callable $callback = null): static 129 | { 130 | } 131 | 132 | /** 133 | * Sets a validator for the question. 134 | * 135 | * @return $this 136 | */ 137 | public function setValidator(?callable $validator = null): static 138 | { 139 | } 140 | 141 | /** 142 | * Gets the validator for the question. 143 | */ 144 | public function getValidator(): ?callable 145 | { 146 | } 147 | 148 | /** 149 | * Sets the maximum number of attempts. 150 | * 151 | * Null means an unlimited number of attempts. 152 | * 153 | * @return $this 154 | * 155 | * @throws InvalidArgumentException in case the number of attempts is invalid 156 | */ 157 | public function setMaxAttempts(?int $attempts): static 158 | { 159 | } 160 | 161 | /** 162 | * Gets the maximum number of attempts. 163 | * 164 | * Null means an unlimited number of attempts. 165 | */ 166 | public function getMaxAttempts(): ?int 167 | { 168 | } 169 | 170 | /** 171 | * Sets a normalizer for the response. 172 | * 173 | * The normalizer can be a callable (a string), a closure or a class implementing __invoke. 174 | * 175 | * @return $this 176 | */ 177 | public function setNormalizer(callable $normalizer): static 178 | { 179 | } 180 | 181 | /** 182 | * Gets the normalizer for the response. 183 | * 184 | * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. 185 | */ 186 | public function getNormalizer(): ?callable 187 | { 188 | } 189 | 190 | /** 191 | * @return bool 192 | */ 193 | protected function isAssoc(array $array) 194 | { 195 | } 196 | 197 | public function isTrimmable(): bool 198 | { 199 | } 200 | 201 | /** 202 | * @return $this 203 | */ 204 | public function setTrimmable(bool $trimmable): static 205 | { 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /tests/stubs/test_traits_usertrait.php: -------------------------------------------------------------------------------- 1 | uid = $uid; 20 | parent::__construct($uid, null, Server::get(IEventDispatcher::class)); 21 | } 22 | 23 | public function getUID(): string { 24 | return $this->uid; 25 | } 26 | } 27 | 28 | /** 29 | * Allow creating users in a temporary backend 30 | */ 31 | trait UserTrait { 32 | /** 33 | * @var \Test\Util\User\Dummy|\OCP\UserInterface 34 | */ 35 | protected $userBackend; 36 | 37 | protected function createUser($name, $password): IUser 38 | { 39 | } 40 | 41 | protected function setUpUserTrait() 42 | { 43 | } 44 | 45 | protected function tearDownUserTrait() 46 | { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/unit/lib/Controller/SlaveControllerTest.php: -------------------------------------------------------------------------------- 1 | request = $this->createMock(IRequest::class); 46 | $this->gss = $this->getMockBuilder(GlobalSiteSelector::class) 47 | ->disableOriginalConstructor()->getMock(); 48 | $this->logger = $this->createMock(LoggerInterface::class); 49 | $this->userSession = $this->createMock(IUserSession::class); 50 | $this->urlGenerator = $this->createMock(IURLGenerator::class); 51 | $this->crypto = $this->createMock(ICrypto::class); 52 | $this->tokenHandler = $this->getMockBuilder(TokenHandler::class) 53 | ->disableOriginalConstructor()->getMock(); 54 | $this->userManager = $this->createMock(IUserManager::class); 55 | $this->userBackend = $this->getMockBuilder(UserBackend::class) 56 | ->disableOriginalConstructor()->getMock(); 57 | $this->session = $this->createMock(ISession::class); 58 | $this->slaveService = $this->createMock(SlaveService::class); 59 | $this->config = $this->createMock(IConfig::class); 60 | } 61 | 62 | /** 63 | * @param array $mockMathods 64 | * @return SlaveController|\PHPUnit_Framework_MockObject_MockObject 65 | */ 66 | private function getInstance(array $mockMathods = []) { 67 | return $this->getMockBuilder(SlaveController::class) 68 | ->setConstructorArgs( 69 | [ 70 | 'gss-tests', 71 | $this->request, 72 | $this->gss, 73 | $this->userSession, 74 | $this->urlGenerator, 75 | $this->crypto, 76 | $this->tokenHandler, 77 | $this->userManager, 78 | $this->userBackend, 79 | $this->session, 80 | $this->slaveService, 81 | $this->config, 82 | $this->logger 83 | ] 84 | )->onlyMethods($mockMathods)->getMock(); 85 | } 86 | 87 | public function testDecodeJwt() { 88 | $controller = $this->getInstance(); 89 | $jwtKey = 'jwtkey'; 90 | $encryptedPassword = 'password-encrypted'; 91 | $plainPassword = 'password'; 92 | 93 | $token = [ 94 | 'uid' => 'user', 95 | 'password' => $encryptedPassword, 96 | 'options' => json_encode(['option1' => 'foo']), 97 | 'exp' => time() + 300, // expires after 5 minutes 98 | ]; 99 | 100 | $jwt = JWT::encode($token, $jwtKey, Application::JWT_ALGORITHM); 101 | 102 | $this->gss->expects($this->any())->method('getJwtKey')->willReturn($jwtKey); 103 | $this->crypto->expects($this->once())->method('decrypt')->with($encryptedPassword, $jwtKey) 104 | ->willReturn($plainPassword); 105 | 106 | [$uid, $password, $options] = $this->invokePrivate($controller, 'decodeJwt', [$jwt]); 107 | 108 | $this->assertSame('user', $uid); 109 | $this->assertSame($plainPassword, $password); 110 | $this->assertSame($options, ['option1' => 'foo']); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/unit/lib/GlobalSiteSelectorTest.php: -------------------------------------------------------------------------------- 1 | config = $this->createMock(IConfig::class); 26 | $this->gss = new GlobalSiteSelector($this->config); 27 | } 28 | 29 | public function testGetMode() { 30 | $this->config->expects($this->once())->method('getSystemValueString') 31 | ->with('gss.mode', 'slave')->willReturn('result'); 32 | 33 | $result = $this->gss->getMode(); 34 | 35 | $this->assertSame('result', $result); 36 | } 37 | 38 | public function testGetJwtKey() { 39 | $this->config->expects($this->once())->method('getSystemValueString') 40 | ->with('gss.jwt.key', '')->willReturn('result'); 41 | 42 | $result = $this->gss->getJwtKey(); 43 | 44 | $this->assertSame('result', $result); 45 | } 46 | 47 | public function testGetMasterUrl() { 48 | $this->config->expects($this->once())->method('getSystemValueString') 49 | ->with('gss.master.url', '')->willReturn('result'); 50 | 51 | $result = $this->gss->getMasterUrl(); 52 | 53 | $this->assertSame('result', $result); 54 | } 55 | 56 | public function testGetLookupServerUrl() { 57 | $this->config->expects($this->once())->method('getSystemValueString') 58 | ->with('lookup_server', '')->willReturn('result'); 59 | 60 | $result = $this->gss->getLookupServerUrl(); 61 | 62 | $this->assertSame('result', $result); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/unit/lib/LookupTest.php: -------------------------------------------------------------------------------- 1 | httpClientService = $this->createMock(IClientService::class); 29 | $this->config = $this->createMock(IConfig::class); 30 | $this->logger = $this->createMock(LoggerInterface::class); 31 | $this->cloudIdManager = $this->createMock(ICloudIdManager::class); 32 | } 33 | 34 | /** 35 | * get Lookup instance 36 | * 37 | * @param array $mockMethods 38 | * @return Lookup|\PHPUnit_Framework_MockObject_MockObject 39 | */ 40 | private function getInstance(array $mockMethods = []) { 41 | return $this->getMockBuilder(Lookup::class) 42 | ->setConstructorArgs( 43 | [ 44 | $this->httpClientService, 45 | $this->logger, 46 | $this->cloudIdManager, 47 | $this->config 48 | ] 49 | )->onlyMethods($mockMethods)->getMock(); 50 | } 51 | 52 | /** 53 | * @param string $lookupServerUrl 54 | * @param string $lookupServerResult 55 | * @param string $userLocation 56 | * @param string $expected 57 | * 58 | * @dataProvider dataTestSearch 59 | */ 60 | public function testSearch($lookupServerUrl, $lookupServerResult, $userLocation, $expected) { 61 | $this->config->expects($this->any())->method('getSystemValueString') 62 | ->with('lookup_server', '')->willReturn($lookupServerUrl); 63 | 64 | $lookup = $this->getInstance(['queryLookupServer', 'getUserLocation']); 65 | $lookup->expects($this->any())->method('queryLookupServer') 66 | ->with('uid')->willReturn($lookupServerResult); 67 | if (isset($lookupServerResult['federationId'])) { 68 | $lookup->expects($this->any())->method('getUserLocation')->with($lookupServerResult['federationId']) 69 | ->willReturn($userLocation); 70 | } 71 | 72 | $userId = 'uid'; 73 | $result = $lookup->search($userId); 74 | 75 | $this->assertSame($expected, $result); 76 | } 77 | 78 | public function dataTestSearch() { 79 | return [ 80 | ['', [], 'location', ''], 81 | ['', ['location' => 'https://nextcloud.com'], 'location', ''], 82 | ['https://lookup.nextcloud.com', ['federationId' => 'user@https://nextcloud.com'], 'https://nextcloud.com', 'https://nextcloud.com'], 83 | ['https://lookup.nextcloud.com', [], 'location', ''], 84 | ]; 85 | } 86 | 87 | // method is not private anymore 88 | // maybe rewrite test with different 'gss.username_format' 89 | // 90 | // public function testGetUserLocation() { 91 | // $lookup = $this->getInstance(); 92 | // $cloudId = $this->createMock(ICloudId::class); 93 | // $federationId = 'user@nextcloud.com'; 94 | // $location = 'nextcloud.com'; 95 | // 96 | // $cloudId->expects($this->once())->method('getRemote') 97 | // ->willReturn($location . '/'); 98 | // 99 | // $this->cloudIdManager->expects($this->once())->method('resolveCloudId') 100 | // ->with($federationId) 101 | // ->willReturn($cloudId); 102 | // 103 | // $result = $this->invokePrivate($lookup, 'getUserLocation', ['user@nextcloud.com']); 104 | // 105 | // $this->assertSame($location, $result); 106 | // } 107 | } 108 | -------------------------------------------------------------------------------- /vendor-bin/csfixer/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "platform": { 4 | "php": "8.0" 5 | }, 6 | "sort-packages": true 7 | }, 8 | "require-dev": { 9 | "nextcloud/coding-standard": "^1.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vendor-bin/mozart/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "platform": { 4 | "php": "8.0" 5 | }, 6 | "sort-packages": true 7 | }, 8 | "require": { 9 | "coenjacobs/mozart": "^0.7.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vendor-bin/phpunit/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "platform": { 4 | "php": "8.0" 5 | }, 6 | "sort-packages": true 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": "^9.6.9" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vendor-bin/psalm/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "platform": { 4 | "php": "8.0" 5 | }, 6 | "sort-packages": true 7 | }, 8 | "require-dev": { 9 | "vimeo/psalm": "^5.13" 10 | } 11 | } 12 | --------------------------------------------------------------------------------