diff --git a/.github/workflows/autocheckers.yml b/.github/workflows/autocheckers.yml index 24f76a4131155..e2ea7852efd21 100644 --- a/.github/workflows/autocheckers.yml +++ b/.github/workflows/autocheckers.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true @@ -77,7 +77,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/block-outdated-3rdparty.yml b/.github/workflows/block-outdated-3rdparty.yml index 3008ef5de0526..0fb2c96201030 100644 --- a/.github/workflows/block-outdated-3rdparty.yml +++ b/.github/workflows/block-outdated-3rdparty.yml @@ -31,7 +31,7 @@ jobs: - 'version.php' - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/block-unconventional-commits.yml b/.github/workflows/block-unconventional-commits.yml index bcc722a618246..4972f8bb33dbe 100644 --- a/.github/workflows/block-unconventional-commits.yml +++ b/.github/workflows/block-unconventional-commits.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7eb7167054fb5..afea0f6149196 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,18 +32,18 @@ jobs: build-mode: none steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} config-file: ./.github/codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/command-compile.yml b/.github/workflows/command-compile.yml index e463769c868d4..3b33c04ada2db 100644 --- a/.github/workflows/command-compile.yml +++ b/.github/workflows/command-compile.yml @@ -103,7 +103,7 @@ jobs: key: git-repo - name: Checkout ${{ needs.init.outputs.head_ref }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false token: ${{ secrets.COMMAND_BOT_PAT }} diff --git a/.github/workflows/command-pull-3rdparty.yml b/.github/workflows/command-pull-3rdparty.yml index 69456ed5290f3..0fafa5e36fd68 100644 --- a/.github/workflows/command-pull-3rdparty.yml +++ b/.github/workflows/command-pull-3rdparty.yml @@ -56,7 +56,7 @@ jobs: id: comment-branch - name: Checkout ${{ steps.comment-branch.outputs.head_ref }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/files-external-ftp.yml b/.github/workflows/files-external-ftp.yml index c3b82d1f7296a..1a5e0b1b230bb 100644 --- a/.github/workflows/files-external-ftp.yml +++ b/.github/workflows/files-external-ftp.yml @@ -62,7 +62,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/files-external-s3.yml b/.github/workflows/files-external-s3.yml index 5d241dc1c2080..669edbbd4d607 100644 --- a/.github/workflows/files-external-s3.yml +++ b/.github/workflows/files-external-s3.yml @@ -71,7 +71,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true @@ -144,7 +144,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/files-external-sftp.yml b/.github/workflows/files-external-sftp.yml index 564e5624a6868..5afa05feb55ca 100644 --- a/.github/workflows/files-external-sftp.yml +++ b/.github/workflows/files-external-sftp.yml @@ -62,7 +62,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/files-external-smb-kerberos.yml b/.github/workflows/files-external-smb-kerberos.yml index a84b40081128c..787147b7e668d 100644 --- a/.github/workflows/files-external-smb-kerberos.yml +++ b/.github/workflows/files-external-smb-kerberos.yml @@ -56,13 +56,13 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true - name: Checkout user_saml - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false repository: nextcloud/user_saml diff --git a/.github/workflows/files-external-smb.yml b/.github/workflows/files-external-smb.yml index fa7203e276cc6..435041e0f8e47 100644 --- a/.github/workflows/files-external-smb.yml +++ b/.github/workflows/files-external-smb.yml @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/files-external-webdav.yml b/.github/workflows/files-external-webdav.yml index 40f9a3aa63988..fb3d3ad9bcc6f 100644 --- a/.github/workflows/files-external-webdav.yml +++ b/.github/workflows/files-external-webdav.yml @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/files-external.yml b/.github/workflows/files-external.yml index 91a6cf2d71de8..dec4a0acdd02d 100644 --- a/.github/workflows/files-external.yml +++ b/.github/workflows/files-external.yml @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/integration-dav.yml b/.github/workflows/integration-dav.yml index fb144825203c8..30151d7c13fae 100644 --- a/.github/workflows/integration-dav.yml +++ b/.github/workflows/integration-dav.yml @@ -53,7 +53,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true @@ -71,7 +71,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up Python - uses: LizardByte/actions/actions/setup_python@a46850981292c4bbc0c41715f286ad95adaaaf4a # v2026.625.20301 + uses: LizardByte/actions/actions/setup_python@d0ae7f82215a479fe2b74f4088c53ee6460513dd # v2026.728.214955 with: python-version: '2.7' diff --git a/.github/workflows/integration-litmus.yml b/.github/workflows/integration-litmus.yml index ee001930ef407..11152a2c5e69b 100644 --- a/.github/workflows/integration-litmus.yml +++ b/.github/workflows/integration-litmus.yml @@ -52,7 +52,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/integration-s3-primary.yml b/.github/workflows/integration-s3-primary.yml index fccaae2004e86..de7894d1a7b92 100644 --- a/.github/workflows/integration-s3-primary.yml +++ b/.github/workflows/integration-s3-primary.yml @@ -76,7 +76,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/integration-sqlite.yml b/.github/workflows/integration-sqlite.yml index 394a22452f70f..d054d22bd6759 100644 --- a/.github/workflows/integration-sqlite.yml +++ b/.github/workflows/integration-sqlite.yml @@ -93,14 +93,14 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true - name: Checkout Talk app if: ${{ matrix.test-suite == 'videoverification_features' }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false repository: nextcloud/spreed @@ -109,7 +109,7 @@ jobs: - name: Checkout Guests app if: ${{ matrix.test-suite == 'guests_features' }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false repository: nextcloud/guests @@ -118,7 +118,7 @@ jobs: - name: Checkout Activity app if: ${{ matrix.test-suite == 'sharing_features' }} - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false repository: nextcloud/activity diff --git a/.github/workflows/lint-eslint.yml b/.github/workflows/lint-eslint.yml index d8777fdbb9a8f..f4adc20fcf6c6 100644 --- a/.github/workflows/lint-eslint.yml +++ b/.github/workflows/lint-eslint.yml @@ -56,7 +56,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/lint-php-cs.yml b/.github/workflows/lint-php-cs.yml index ef06cf30958f4..8ddaed065f0fc 100644 --- a/.github/workflows/lint-php-cs.yml +++ b/.github/workflows/lint-php-cs.yml @@ -51,7 +51,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml index 3130dd2ff6716..71c15d2b3007a 100644 --- a/.github/workflows/lint-php.yml +++ b/.github/workflows/lint-php.yml @@ -53,7 +53,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/lint-stylelint.yml b/.github/workflows/lint-stylelint.yml index a921ab7b829d7..58e70dd5cb297 100644 --- a/.github/workflows/lint-stylelint.yml +++ b/.github/workflows/lint-stylelint.yml @@ -53,7 +53,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 4f2f28c67e721..4fdd8cf99ab8b 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/npm-audit-fix.yml b/.github/workflows/npm-audit-fix.yml index 1d0e7244eb484..928fc93050909 100644 --- a/.github/workflows/npm-audit-fix.yml +++ b/.github/workflows/npm-audit-fix.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ matrix.branches }} diff --git a/.github/workflows/npm-build.yml b/.github/workflows/npm-build.yml index ada54e9080b2f..571057f49837e 100644 --- a/.github/workflows/npm-build.yml +++ b/.github/workflows/npm-build.yml @@ -56,7 +56,7 @@ jobs: name: NPM build steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/object-storage-azure.yml b/.github/workflows/object-storage-azure.yml index 899d5550cd2b8..f112b6222988e 100644 --- a/.github/workflows/object-storage-azure.yml +++ b/.github/workflows/object-storage-azure.yml @@ -76,7 +76,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/object-storage-s3.yml b/.github/workflows/object-storage-s3.yml index 43a9ebd3fad62..0d74ff7e517f5 100644 --- a/.github/workflows/object-storage-s3.yml +++ b/.github/workflows/object-storage-s3.yml @@ -77,7 +77,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/object-storage-swift.yml b/.github/workflows/object-storage-swift.yml index e76df8c869fd2..f780fd26983d6 100644 --- a/.github/workflows/object-storage-swift.yml +++ b/.github/workflows/object-storage-swift.yml @@ -74,7 +74,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 19771f30c8b38..ae17221e717bf 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 40e9412265cbf..a3a7dd5cf4b96 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout server before PR - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-32bits.yml b/.github/workflows/phpunit-32bits.yml index 1addd9da5b968..0bcbb786e25a4 100644 --- a/.github/workflows/phpunit-32bits.yml +++ b/.github/workflows/phpunit-32bits.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-kvstore.yml b/.github/workflows/phpunit-kvstore.yml index 3e8c4eba19bd3..44be8c430d874 100644 --- a/.github/workflows/phpunit-kvstore.yml +++ b/.github/workflows/phpunit-kvstore.yml @@ -24,7 +24,7 @@ jobs: src: ${{ steps.changes.outputs.src}} steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: changes continue-on-error: true with: @@ -52,7 +52,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-mariadb.yml b/.github/workflows/phpunit-mariadb.yml index e42d27957f0be..329e6bdfd49c2 100644 --- a/.github/workflows/phpunit-mariadb.yml +++ b/.github/workflows/phpunit-mariadb.yml @@ -87,7 +87,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-memcached.yml b/.github/workflows/phpunit-memcached.yml index 6c137ed9777a4..0c4e8c8e279eb 100644 --- a/.github/workflows/phpunit-memcached.yml +++ b/.github/workflows/phpunit-memcached.yml @@ -68,7 +68,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-mysql-sharding.yml b/.github/workflows/phpunit-mysql-sharding.yml index ba38fadb6f884..c1d94d1fadd60 100644 --- a/.github/workflows/phpunit-mysql-sharding.yml +++ b/.github/workflows/phpunit-mysql-sharding.yml @@ -124,7 +124,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml index 82fc2183b3d5a..0f246d9cbf8d3 100644 --- a/.github/workflows/phpunit-mysql.yml +++ b/.github/workflows/phpunit-mysql.yml @@ -87,7 +87,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-nodb.yml b/.github/workflows/phpunit-nodb.yml index cafef939fad44..9806d0a81f6c9 100644 --- a/.github/workflows/phpunit-nodb.yml +++ b/.github/workflows/phpunit-nodb.yml @@ -72,7 +72,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-object-store-primary.yml b/.github/workflows/phpunit-object-store-primary.yml index ec5f01974b5f9..0f178d630826e 100644 --- a/.github/workflows/phpunit-object-store-primary.yml +++ b/.github/workflows/phpunit-object-store-primary.yml @@ -72,7 +72,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml index dbddc5a56b6a3..1416c3b31eec2 100644 --- a/.github/workflows/phpunit-oci.yml +++ b/.github/workflows/phpunit-oci.yml @@ -94,7 +94,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml index d2ae31d992fd2..a940de86199dd 100644 --- a/.github/workflows/phpunit-pgsql.yml +++ b/.github/workflows/phpunit-pgsql.yml @@ -86,7 +86,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml index 95792c2a181d5..c7572e91ae502 100644 --- a/.github/workflows/phpunit-sqlite.yml +++ b/.github/workflows/phpunit-sqlite.yml @@ -72,7 +72,7 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 1ff86fad47828..601fce8d0a183 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest needs: gate steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true # for 3rdparty @@ -59,7 +59,7 @@ jobs: uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0 id: versions - name: Set up node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ steps.versions.outputs.node-version }} - name: Set up npm @@ -96,7 +96,7 @@ jobs: key: playwright-context-${{ github.run_id }} path: ./ - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: steps.cache.outputs.cache-hit != 'true' with: persist-credentials: false @@ -107,7 +107,7 @@ jobs: id: versions - name: Set up node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ steps.versions.outputs.node-version }} @@ -203,7 +203,7 @@ jobs: key: playwright-context-${{ github.run_id }} path: ./ - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: steps.cache.outputs.cache-hit != 'true' with: persist-credentials: false @@ -214,7 +214,7 @@ jobs: id: versions - name: Set up node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ steps.versions.outputs.node-version }} @@ -267,12 +267,12 @@ jobs: key: playwright-context-${{ github.run_id }} path: ./ - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: steps.cache.outputs.cache-hit != 'true' with: persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 if: steps.cache.outputs.cache-hit != 'true' with: node-version: ${{ needs.playwright-tests.outputs.node-version }} diff --git a/.github/workflows/rector-apply.yml b/.github/workflows/rector-apply.yml index 835dc737146b8..c0913c8f69703 100644 --- a/.github/workflows/rector-apply.yml +++ b/.github/workflows/rector-apply.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 34e8db106d398..5c843593c5016 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml index 4e1a74194ecf7..67fdafc440e24 100644 --- a/.github/workflows/reuse.yml +++ b/.github/workflows/reuse.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest-low steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 63d18bd2eda67..ad13363290151 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -20,7 +20,7 @@ jobs: issues: write steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v9 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v9 with: repo-token: ${{ secrets.COMMAND_BOT_PAT }} stale-issue-message: > diff --git a/.github/workflows/static-code-analysis.yml b/.github/workflows/static-code-analysis.yml index 9434db55a8b42..2014c7cd86f77 100644 --- a/.github/workflows/static-code-analysis.yml +++ b/.github/workflows/static-code-analysis.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false submodules: true @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false submodules: true @@ -116,7 +116,7 @@ jobs: - name: Upload Security Analysis results to GitHub if: always() - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v3 + uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v3 with: sarif_file: results.sarif @@ -128,7 +128,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false submodules: true @@ -161,7 +161,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false submodules: true @@ -190,7 +190,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false submodules: true diff --git a/.github/workflows/update-cacert-bundle.yml b/.github/workflows/update-cacert-bundle.yml index 2adc0baa78c87..ac619aa4a4831 100644 --- a/.github/workflows/update-cacert-bundle.yml +++ b/.github/workflows/update-cacert-bundle.yml @@ -36,7 +36,7 @@ jobs: name: update-ca-certificate-bundle-${{ matrix.branches }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ matrix.branches }} diff --git a/.github/workflows/update-code-signing-crl.yml b/.github/workflows/update-code-signing-crl.yml index 6793ef7ad3bd6..7db554c1b4564 100644 --- a/.github/workflows/update-code-signing-crl.yml +++ b/.github/workflows/update-code-signing-crl.yml @@ -36,7 +36,7 @@ jobs: name: update-code-signing-crl-${{ matrix.branches }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ matrix.branches }} diff --git a/.github/workflows/update-min-supported-desktop.yml b/.github/workflows/update-min-supported-desktop.yml index 92ecdfcdba3b7..9c6648d7dc04b 100644 --- a/.github/workflows/update-min-supported-desktop.yml +++ b/.github/workflows/update-min-supported-desktop.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest-low steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false submodules: true diff --git a/apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php b/apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php index e2a118ca572db..785b98dc69648 100644 --- a/apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php +++ b/apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php @@ -51,7 +51,7 @@ public function beforeHandler(RequestInterface $request) { return; } - $minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.2.82'); + $minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.3.50'); $maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99'); // Check if the client is a desktop client diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index e35f466e63120..ae68770d9a90c 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -322,7 +322,14 @@ public function put($data) { $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); $fileExists = $storage->file_exists($internalPath); if ($renameOkay === false || $fileExists === false) { - Server::get(LoggerInterface::class)->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']); + Server::get(LoggerInterface::class) + ->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', [ + 'app' => 'webdav', + 'source_storage' => $partStorage->getId(), + 'target_storage' => $storage->getId(), + 'source_internal_path' => $internalPartPath, + 'target_internal_path' => $internalPath, + ]); throw new Exception($this->l10n->t('Could not rename part file to final file')); } } catch (ForbiddenException $ex) { diff --git a/apps/provisioning_api/lib/Controller/AppConfigController.php b/apps/provisioning_api/lib/Controller/AppConfigController.php index ade9e8a73c84a..edee8f28695c7 100644 --- a/apps/provisioning_api/lib/Controller/AppConfigController.php +++ b/apps/provisioning_api/lib/Controller/AppConfigController.php @@ -15,6 +15,7 @@ use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; @@ -102,8 +103,6 @@ public function getValue(string $app, string $key, string $defaultValue = ''): D } /** - * @NoSubAdminRequired - * * Update the config value of an app * * @param string $app ID of the app @@ -116,6 +115,7 @@ public function getValue(string $app, string $key, string $defaultValue = ''): D */ #[PasswordConfirmationRequired] #[NoAdminRequired] + #[NoSubAdminRequired] public function setValue(string $app, string $key, string $value): DataResponse { $user = $this->userSession->getUser(); if ($user === null) { diff --git a/apps/provisioning_api/lib/Controller/GroupsController.php b/apps/provisioning_api/lib/Controller/GroupsController.php index bcae81099c381..1c1be04c88ded 100644 --- a/apps/provisioning_api/lib/Controller/GroupsController.php +++ b/apps/provisioning_api/lib/Controller/GroupsController.php @@ -17,6 +17,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSException; @@ -138,8 +139,6 @@ public function getGroup(string $groupId): DataResponse { } /** - * @NoSubAdminRequired - * * Get a list of users in the specified group * * @param string $groupId ID of the group @@ -151,6 +150,7 @@ public function getGroup(string $groupId): DataResponse { * 200: User IDs returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getGroupUsers(string $groupId): DataResponse { $groupId = urldecode($groupId); diff --git a/apps/provisioning_api/lib/Controller/PreferencesController.php b/apps/provisioning_api/lib/Controller/PreferencesController.php index 8ae64e65b81d9..ee272d810b07d 100644 --- a/apps/provisioning_api/lib/Controller/PreferencesController.php +++ b/apps/provisioning_api/lib/Controller/PreferencesController.php @@ -11,6 +11,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; use OCP\Config\BeforePreferenceDeletedEvent; @@ -33,8 +34,6 @@ public function __construct( } /** - * @NoSubAdminRequired - * * Update multiple preference values of an app * * @param string $appId ID of the app @@ -46,6 +45,7 @@ public function __construct( * 400: Preference invalid */ #[NoAdminRequired] + #[NoSubAdminRequired] public function setMultiplePreferences(string $appId, array $configs): DataResponse { $userId = $this->userSession->getUser()->getUID(); @@ -78,8 +78,6 @@ public function setMultiplePreferences(string $appId, array $configs): DataRespo } /** - * @NoSubAdminRequired - * * Update a preference value of an app * * @param string $appId ID of the app @@ -91,6 +89,7 @@ public function setMultiplePreferences(string $appId, array $configs): DataRespo * 400: Preference invalid */ #[NoAdminRequired] + #[NoSubAdminRequired] public function setPreference(string $appId, string $configKey, string $configValue): DataResponse { $userId = $this->userSession->getUser()->getUID(); @@ -119,8 +118,6 @@ public function setPreference(string $appId, string $configKey, string $configVa } /** - * @NoSubAdminRequired - * * Delete multiple preferences for an app * * @param string $appId ID of the app @@ -132,6 +129,7 @@ public function setPreference(string $appId, string $configKey, string $configVa * 400: Preference invalid */ #[NoAdminRequired] + #[NoSubAdminRequired] public function deleteMultiplePreference(string $appId, array $configKeys): DataResponse { $userId = $this->userSession->getUser()->getUID(); @@ -162,8 +160,6 @@ public function deleteMultiplePreference(string $appId, array $configKeys): Data } /** - * @NoSubAdminRequired - * * Delete a preference for an app * * @param string $appId ID of the app @@ -174,6 +170,7 @@ public function deleteMultiplePreference(string $appId, array $configKeys): Data * 400: Preference invalid */ #[NoAdminRequired] + #[NoSubAdminRequired] public function deletePreference(string $appId, string $configKey): DataResponse { $userId = $this->userSession->getUser()->getUID(); diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 370528af6d909..f46084a98741e 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -26,6 +26,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataResponse; @@ -349,8 +350,6 @@ public function getLastLoggedInUsers( } /** - * @NoSubAdminRequired - * * Search users by their phone numbers * * @param string $location Location of the phone number (for country code) @@ -360,6 +359,7 @@ public function getLastLoggedInUsers( * 200: Users returned * 400: Invalid location */ + #[NoSubAdminRequired] #[NoAdminRequired] public function searchByPhoneNumbers(string $location, array $search): DataResponse { if ($this->phoneNumberUtil->getCountryCodeForRegion($location) === null) { @@ -597,7 +597,7 @@ public function addUser( // Send new user mail only if a mail is set if ($email !== '') { $newUser->setSystemEMailAddress($email); - if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') { + if ($this->appConfig->getValueBool('core', 'newUser.sendEmail', true)) { try { $emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken); $this->newUserMailHelper->sendMail($newUser, $emailTemplate); @@ -656,8 +656,6 @@ public function addUser( } /** - * @NoSubAdminRequired - * * Get the details of a user * * @param string $userId ID of the user @@ -667,6 +665,7 @@ public function addUser( * 200: User returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getUser(string $userId): DataResponse { $includeScopes = false; $currentUser = $this->userSession->getUser(); @@ -683,8 +682,6 @@ public function getUser(string $userId): DataResponse { } /** - * @NoSubAdminRequired - * * Get the details of the current user * * @return DataResponse @@ -693,6 +690,7 @@ public function getUser(string $userId): DataResponse { * 200: Current user returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getCurrentUser(): DataResponse { $user = $this->userSession->getUser(); if ($user) { @@ -705,8 +703,6 @@ public function getCurrentUser(): DataResponse { } /** - * @NoSubAdminRequired - * * Get a list of fields that are editable for the current user * * @return DataResponse, array{}> @@ -715,6 +711,7 @@ public function getCurrentUser(): DataResponse { * 200: Editable fields returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getEditableFields(): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); if (!$currentLoggedInUser instanceof IUser) { @@ -738,8 +735,6 @@ public function getEnabledApps(): DataResponse { } /** - * @NoSubAdminRequired - * * Get a list of fields that are editable for a user * * @param string $userId ID of the user @@ -749,6 +744,7 @@ public function getEnabledApps(): DataResponse { * 200: Editable fields for user returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getEditableFieldsForUser(string $userId): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); if (!$currentLoggedInUser instanceof IUser) { @@ -794,8 +790,6 @@ public function getEditableFieldsForUser(string $userId): DataResponse { } /** - * @NoSubAdminRequired - * * Update multiple values of the user's details * * @param string $userId ID of the user @@ -809,6 +803,7 @@ public function getEditableFieldsForUser(string $userId): DataResponse { */ #[PasswordConfirmationRequired] #[NoAdminRequired] + #[NoSubAdminRequired] #[UserRateLimit(limit: 5, period: 60)] public function editUserMultiValue( string $userId, @@ -1173,8 +1168,6 @@ private function validatePasswordChange(IUser $targetUser, string $password): ?a } /** - * @NoSubAdminRequired - * * Update a value of the user's details * * @param string $userId ID of the user @@ -1187,6 +1180,7 @@ private function validatePasswordChange(IUser $targetUser, string $password): ?a */ #[PasswordConfirmationRequired] #[NoAdminRequired] + #[NoSubAdminRequired] #[UserRateLimit(limit: 50, period: 600)] public function editUser(string $userId, string $key, string $value): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); @@ -1611,8 +1605,6 @@ private function setEnabled(string $userId, bool $value): DataResponse { } /** - * @NoSubAdminRequired - * * Get a list of groups the user belongs to * * @param string $userId ID of the user @@ -1622,6 +1614,7 @@ private function setEnabled(string $userId, bool $value): DataResponse { * 200: Users groups returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getUsersGroups(string $userId): DataResponse { $loggedInUser = $this->userSession->getUser(); @@ -1656,8 +1649,6 @@ public function getUsersGroups(string $userId): DataResponse { } /** - * @NoSubAdminRequired - * * Get a list of groups with details * * @param string $userId ID of the user @@ -1667,6 +1658,7 @@ public function getUsersGroups(string $userId): DataResponse { * 200: Users groups returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getUsersGroupsDetails(string $userId): DataResponse { $loggedInUser = $this->userSession->getUser(); @@ -1733,8 +1725,6 @@ function (string $gid) { } /** - * @NoSubAdminRequired - * * Get a list of the groups the user is a subadmin of, with details * * @param string $userId ID of the user @@ -1744,6 +1734,7 @@ function (string $gid) { * 200: Users subadmin groups returned */ #[NoAdminRequired] + #[NoSubAdminRequired] public function getUserSubAdminGroupsDetails(string $userId): DataResponse { $loggedInUser = $this->userSession->getUser(); diff --git a/apps/provisioning_api/lib/Controller/VerificationController.php b/apps/provisioning_api/lib/Controller/VerificationController.php index d5a51a95ad07f..9472038d9b15b 100644 --- a/apps/provisioning_api/lib/Controller/VerificationController.php +++ b/apps/provisioning_api/lib/Controller/VerificationController.php @@ -15,6 +15,7 @@ use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\TemplateResponse; use OCP\HintException; @@ -41,9 +42,7 @@ public function __construct( parent::__construct($appName, $request); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[NoCSRFRequired] public function showVerifyMail(string $token, string $userId, string $key): TemplateResponse { @@ -71,9 +70,7 @@ public function showVerifyMail(string $token, string $userId, string $key): Temp ], TemplateResponse::RENDER_AS_GUEST); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[BruteForceProtection(action: 'emailVerification')] public function verifyMail(string $token, string $userId, string $key): TemplateResponse { diff --git a/apps/provisioning_api/lib/Middleware/ProvisioningApiMiddleware.php b/apps/provisioning_api/lib/Middleware/ProvisioningApiMiddleware.php index 9f8f28d9197c6..d2730a8c30f2c 100644 --- a/apps/provisioning_api/lib/Middleware/ProvisioningApiMiddleware.php +++ b/apps/provisioning_api/lib/Middleware/ProvisioningApiMiddleware.php @@ -13,7 +13,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; -use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Middleware; use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\Utility\IControllerMethodReflector; @@ -43,7 +43,8 @@ public function __construct( #[\Override] public function beforeController(Controller $controller, string $methodName): void { // If AuthorizedAdminSetting, the check will be done in the SecurityMiddleware - if (!$this->isAdmin && !$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->isSubAdmin && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { + if (!$this->isAdmin && !$this->reflector->hasAnnotationOrAttribute('NoSubAdminRequired', NoSubAdminRequired::class) + && !$this->isSubAdmin && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { throw new NotSubAdminException(); } } @@ -53,10 +54,9 @@ public function beforeController(Controller $controller, string $methodName): vo * @param string $methodName * @param \Exception $exception * @throws \Exception - * @return Response */ #[\Override] - public function afterException(Controller $controller, string $methodName, \Exception $exception) { + public function afterException(Controller $controller, string $methodName, \Exception $exception): never { if ($exception instanceof NotSubAdminException) { throw new OCSException($exception->getMessage(), Http::STATUS_FORBIDDEN); } diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index a565199e671b1..08355a5cb1804 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -734,6 +734,67 @@ public function testAddUserSuccessfulGeneratePassword(): void { )); } + /** + * `newUser.sendEmail` has to be read as a boolean. It is stored as an untyped + * 'yes'/'no' string on instances created before Nextcloud 33 and as a typed + * boolean once the account settings toggle has been used, so comparing it to + * the string 'yes' silently skipped the mail on upgraded instances. + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataAddUserWelcomeMail')] + public function testAddUserSendsWelcomeMailWhenEnabled(bool $enabled): void { + $this->appConfig + ->expects($this->atLeastOnce()) + ->method('getValueBool') + ->with('core', 'newUser.sendEmail', true) + ->willReturn($enabled); + + $newUser = $this->createMock(IUser::class); + $newUser->expects($this->once()) + ->method('setSystemEMailAddress') + ->with('foo@bar.com'); + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('NewUser') + ->willReturn(false); + $this->userManager + ->expects($this->once()) + ->method('createUser') + ->willReturn($newUser); + $loggedInUser = $this->createMock(IUser::class); + $loggedInUser + ->method('getUID') + ->willReturn('adminUser'); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($loggedInUser); + $this->groupManager + ->expects($this->once()) + ->method('isAdmin') + ->with('adminUser') + ->willReturn(true); + + $emailTemplate = $this->createMock(IEMailTemplate::class); + $this->newUserMailHelper + ->expects($enabled ? $this->once() : $this->never()) + ->method('generateTemplate') + ->willReturn($emailTemplate); + $this->newUserMailHelper + ->expects($enabled ? $this->once() : $this->never()) + ->method('sendMail') + ->with($newUser, $emailTemplate); + + $this->api->addUser('NewUser', 'PasswordOfTheNewUser', '', 'foo@bar.com'); + } + + public static function dataAddUserWelcomeMail(): array { + return [ + 'enabled' => [true], + 'disabled' => [false], + ]; + } + public function testAddUserSuccessfulLowercaseEmail(): void { $this->userManager ->expects($this->once()) diff --git a/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php b/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php index 15760f21e16d0..0d1df6f8c9790 100644 --- a/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php +++ b/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php @@ -53,15 +53,11 @@ public function testBeforeController(bool $subadminRequired, bool $isAdmin, bool $isSubAdmin ); - $this->reflector->method('hasAnnotation') - ->willReturnCallback(function ($annotation) use ($subadminRequired) { + $this->reflector->method('hasAnnotationOrAttribute') + ->willReturnCallback(function ($annotation, $attribute) use ($subadminRequired, $hasSettingAuthorizationAnnotation) { if ($annotation === 'NoSubAdminRequired') { return !$subadminRequired; } - return false; - }); - $this->reflector->method('hasAnnotationOrAttribute') - ->willReturnCallback(function ($annotation, $attribute) use ($hasSettingAuthorizationAnnotation) { if ($annotation === 'AuthorizedAdminSetting') { return $hasSettingAuthorizationAnnotation; } diff --git a/apps/settings/lib/Controller/AdminSettingsController.php b/apps/settings/lib/Controller/AdminSettingsController.php index cd02d68ec3800..c6cfe2a2b7cfc 100644 --- a/apps/settings/lib/Controller/AdminSettingsController.php +++ b/apps/settings/lib/Controller/AdminSettingsController.php @@ -10,6 +10,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; @@ -47,10 +48,10 @@ public function __construct( } /** - * @NoSubAdminRequired * We are checking the permissions in the getSettings method. If there is no allowed * settings for the given section. The user will be greeted by an error message. */ + #[NoSubAdminRequired] #[NoAdminRequired] #[NoCSRFRequired] public function index(string $section): TemplateResponse { diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php index afdc13b76253f..a0e82ed98d1e8 100644 --- a/apps/settings/lib/Controller/AuthSettingsController.php +++ b/apps/settings/lib/Controller/AuthSettingsController.php @@ -20,6 +20,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Services\IAppConfig; @@ -56,10 +57,9 @@ public function __construct( } /** - * @NoSubAdminRequired - * * @param bool $qrcodeLogin If set to true, the returned token could be (depending on server settings) a onetime password, that can only be used to get the actual app password a single time */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired(strict: true)] public function create(string $name = '', bool $qrcodeLogin = false): JSONResponse { @@ -139,10 +139,7 @@ public function create(string $name = '', bool $qrcodeLogin = false): JSONRespon ]); } - /** - * @return JSONResponse - */ - private function getServiceNotAvailableResponse() { + private function getServiceNotAvailableResponse(): JSONResponse { $resp = new JSONResponse(); $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); return $resp; @@ -152,10 +149,8 @@ private function getServiceNotAvailableResponse() { * Return a 25 digit device password * * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456 - * - * @return string */ - private function generateRandomDeviceToken() { + private function generateRandomDeviceToken(): string { $groups = []; for ($i = 0; $i < 5; $i++) { $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE); @@ -167,15 +162,10 @@ private function checkAppToken(): bool { return $this->session->exists('app_password'); } - /** - * @NoSubAdminRequired - * - * @param int $id - * @return array|JSONResponse - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired(strict: true)] - public function destroy($id) { + public function destroy(int $id): JSONResponse { if ($this->checkAppToken()) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); } @@ -195,20 +185,13 @@ public function destroy($id) { $this->tokenProvider->invalidateTokenById($this->userId, $token->getId()); $this->publishActivity($subject, $token->getId(), ['name' => $token->getName()]); - return []; + return new JSONResponse([]); } - /** - * @NoSubAdminRequired - * - * @param int $id - * @param array $scope - * @param string $name - * @return array|JSONResponse - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired(strict: true)] - public function update($id, array $scope, string $name) { + public function update(int $id, array $scope, string $name): JSONResponse { if ($this->checkAppToken()) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); } @@ -236,14 +219,9 @@ public function update($id, array $scope, string $name) { } $this->tokenProvider->updateToken($token); - return []; + return new JSONResponse([]); } - /** - * @param string $subject - * @param int $id - * @param array $parameters - */ private function publishActivity(string $subject, int $id, array $parameters = []): void { $event = $this->activityManager->generateEvent(); $event->setApp('settings') @@ -263,8 +241,6 @@ private function publishActivity(string $subject, int $id, array $parameters = [ /** * Find a token by given id and check if uid for current session belongs to this token * - * @param int $id - * @return IToken * @throws InvalidTokenException */ private function findTokenByIdAndUser(int $id): IToken { @@ -281,13 +257,10 @@ private function findTokenByIdAndUser(int $id): IToken { } /** - * @NoSubAdminRequired - * - * @param int $id - * @return JSONResponse * @throws InvalidTokenException * @throws ExpiredTokenException */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] public function wipe(int $id): JSONResponse { diff --git a/apps/settings/lib/Controller/ChangePasswordController.php b/apps/settings/lib/Controller/ChangePasswordController.php index 3c1fcd0a9998a..b8e658db83ab9 100644 --- a/apps/settings/lib/Controller/ChangePasswordController.php +++ b/apps/settings/lib/Controller/ChangePasswordController.php @@ -18,6 +18,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\HintException; @@ -41,9 +42,7 @@ public function __construct( parent::__construct($appName, $request); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[BruteForceProtection(action: 'changePersonalPassword')] public function changePersonalPassword(string $oldpassword = '', ?string $newpassword = null): JSONResponse { diff --git a/apps/settings/lib/Controller/DeclarativeSettingsController.php b/apps/settings/lib/Controller/DeclarativeSettingsController.php index 4e4bee4043ca0..f233a5d7bbf6a 100644 --- a/apps/settings/lib/Controller/DeclarativeSettingsController.php +++ b/apps/settings/lib/Controller/DeclarativeSettingsController.php @@ -15,6 +15,7 @@ use OCA\Settings\ResponseDefinitions; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; @@ -115,10 +116,10 @@ private function saveValue(string $app, string $formId, string $fieldId, mixed $ * * @return DataResponse, array{}> * @throws NotLoggedInException - * @NoSubAdminRequired * * 200: Forms returned */ + #[NoSubAdminRequired] #[NoAdminRequired] public function getForms(): DataResponse { $user = $this->userSession->getUser(); diff --git a/apps/settings/lib/Controller/HelpController.php b/apps/settings/lib/Controller/HelpController.php index e5e70998cd680..a0bf69e6de572 100644 --- a/apps/settings/lib/Controller/HelpController.php +++ b/apps/settings/lib/Controller/HelpController.php @@ -12,6 +12,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\TemplateResponse; @@ -41,13 +42,9 @@ public function __construct( parent::__construct($appName, $request); } - /** - * @return TemplateResponse - * - * @NoSubAdminRequired - */ #[NoCSRFRequired] #[NoAdminRequired] + #[NoSubAdminRequired] public function help(string $mode = 'user'): TemplateResponse { $this->navigationManager->setActiveEntry('help'); $pageTitle = $this->l10n->t('Administrator documentation'); diff --git a/apps/settings/lib/Controller/PersonalSettingsController.php b/apps/settings/lib/Controller/PersonalSettingsController.php index be74b2ed0c7a8..99eafc3a5ad9a 100644 --- a/apps/settings/lib/Controller/PersonalSettingsController.php +++ b/apps/settings/lib/Controller/PersonalSettingsController.php @@ -10,6 +10,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; @@ -46,10 +47,8 @@ public function __construct( $this->initialState = $initialState; } - /** - * @NoSubAdminRequired - */ #[NoAdminRequired] + #[NoSubAdminRequired] #[NoCSRFRequired] public function index(string $section): TemplateResponse { return $this->getIndexResponse( diff --git a/apps/settings/lib/Controller/ReasonsController.php b/apps/settings/lib/Controller/ReasonsController.php index f96797695a005..7975f0a360762 100644 --- a/apps/settings/lib/Controller/ReasonsController.php +++ b/apps/settings/lib/Controller/ReasonsController.php @@ -12,18 +12,16 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\DataDisplayResponse; #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class ReasonsController extends Controller { - - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[NoCSRFRequired] - public function getPdf() { + public function getPdf(): DataDisplayResponse { $data = file_get_contents(__DIR__ . '/../../data/Reasons to use Nextcloud.pdf'); $resp = new DataDisplayResponse($data); diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php index 19da07e854b49..82e501262d0d3 100644 --- a/apps/settings/lib/Controller/UsersController.php +++ b/apps/settings/lib/Controller/UsersController.php @@ -31,6 +31,7 @@ use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\Attribute\UserRateLimit; @@ -329,32 +330,8 @@ protected function canAdminChangeUserPasswords(): bool { return $canChangePassword; } - /** - * @NoSubAdminRequired - * - * @param string|null $avatarScope - * @param string|null $displayname - * @param string|null $displaynameScope - * @param string|null $phone - * @param string|null $phoneScope - * @param string|null $email - * @param string|null $emailScope - * @param string|null $website - * @param string|null $websiteScope - * @param string|null $address - * @param string|null $addressScope - * @param string|null $twitter - * @param string|null $twitterScope - * @param string|null $bluesky - * @param string|null $blueskyScope - * @param string|null $fediverse - * @param string|null $fediverseScope - * @param string|null $birthdate - * @param string|null $birthdateScope - * - * @return DataResponse - */ #[NoAdminRequired] + #[NoSubAdminRequired] #[PasswordConfirmationRequired] #[UserRateLimit(limit: 5, period: 60)] public function setUserSettings(?string $avatarScope = null, @@ -378,7 +355,7 @@ public function setUserSettings(?string $avatarScope = null, ?string $birthdateScope = null, ?string $pronouns = null, ?string $pronounsScope = null, - ) { + ): DataResponse { $user = $this->userSession->getUser(); if (!$user instanceof IUser) { return new DataResponse( @@ -523,12 +500,9 @@ protected function saveUserSettings(IAccount $userAccount): void { /** * Set the mail address of a user * - * @NoSubAdminRequired - * - * @param string $account * @param bool $onlyVerificationCode only return verification code without updating the data - * @return DataResponse */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse { diff --git a/apps/settings/lib/Controller/WebAuthnController.php b/apps/settings/lib/Controller/WebAuthnController.php index 1f2f1e0ceb228..d61a2a8904328 100644 --- a/apps/settings/lib/Controller/WebAuthnController.php +++ b/apps/settings/lib/Controller/WebAuthnController.php @@ -15,6 +15,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\Attribute\UseSession; @@ -39,9 +40,7 @@ public function __construct( parent::__construct(Application::APP_ID, $request); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] #[UseSession] @@ -57,9 +56,7 @@ public function startRegistration(): JSONResponse { return new JSONResponse($credentialOptions); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] #[UseSession] @@ -79,9 +76,7 @@ public function finishRegistration(string $name, string $data): JSONResponse { return new JSONResponse($this->manager->finishRegister($publicKeyCredentialCreationOptions, $name, $data)); } - /** - * @NoSubAdminRequired - */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] public function deleteRegistration(int $id): JSONResponse { diff --git a/apps/settings/lib/Middleware/SubadminMiddleware.php b/apps/settings/lib/Middleware/SubadminMiddleware.php index 24d443a55d2f6..7074bb8e4788b 100644 --- a/apps/settings/lib/Middleware/SubadminMiddleware.php +++ b/apps/settings/lib/Middleware/SubadminMiddleware.php @@ -15,6 +15,7 @@ use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Middleware; use OCP\Group\ISubAdmin; @@ -24,7 +25,7 @@ /** * Verifies whether a user has at least sub-admin rights. - * To bypass use the `@NoSubAdminRequired` annotation + * To bypass use the `#[NoSubAdminRequired]` annotation */ class SubadminMiddleware extends Middleware { public function __construct( @@ -45,7 +46,7 @@ private function isSubAdmin(): bool { #[Override] public function beforeController(Controller $controller, string $methodName): void { - if (!$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { + if (!$this->reflector->hasAnnotationOrAttribute('NoSubAdminRequired', NoSubAdminRequired::class) && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { if (!$this->isSubAdmin()) { throw new NotAdminException($this->l10n->t('Logged in account must be a sub admin')); } diff --git a/apps/settings/tests/Controller/AuthSettingsControllerTest.php b/apps/settings/tests/Controller/AuthSettingsControllerTest.php index f3503e3c2d7f3..c8886f1c35f8f 100644 --- a/apps/settings/tests/Controller/AuthSettingsControllerTest.php +++ b/apps/settings/tests/Controller/AuthSettingsControllerTest.php @@ -207,7 +207,7 @@ public function testDestroy(): void { ->method('invalidateTokenById') ->with($this->uid, $tokenId); - $this->assertEquals([], $this->controller->destroy($tokenId)); + $this->assertEquals([], $this->controller->destroy($tokenId)->getData()); } public function testDestroyExpired(): void { @@ -231,7 +231,7 @@ public function testDestroyExpired(): void { ->method('invalidateTokenById') ->with($this->uid, $tokenId); - $this->assertSame([], $this->controller->destroy($tokenId)); + $this->assertSame([], $this->controller->destroy($tokenId)->getData()); } public function testDestroyWipePendingEmitsCancelledSubject(): void { @@ -268,7 +268,7 @@ public function testDestroyWipePendingEmitsCancelledSubject(): void { $this->activityManager->expects($this->once()) ->method('publish'); - $this->assertEquals([], $this->controller->destroy($tokenId)); + $this->assertEquals([], $this->controller->destroy($tokenId)->getData()); } public function testDestroyWrongUser(): void { @@ -321,7 +321,7 @@ public function testUpdateRename(string $name, string $newName): void { ->method('updateToken') ->with($this->equalTo($token)); - $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], $newName)); + $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], $newName)->getData()); } public static function dataUpdateFilesystemScope(): array { @@ -359,7 +359,7 @@ public function testUpdateFilesystemScope(bool $filesystem, bool $newFilesystem) ->method('updateToken') ->with($this->equalTo($token)); - $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => $newFilesystem], 'App password')); + $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => $newFilesystem], 'App password')->getData()); } public function testUpdateNoChange(): void { @@ -390,7 +390,7 @@ public function testUpdateNoChange(): void { ->method('updateToken') ->with($this->equalTo($token)); - $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password')); + $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password')->getData()); } public function testUpdateExpired(): void { @@ -410,7 +410,7 @@ public function testUpdateExpired(): void { ->method('updateToken') ->with($this->equalTo($token)); - $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password')); + $this->assertSame([], $this->controller->update($tokenId, [IToken::SCOPE_FILESYSTEM => true], 'App password')->getData()); } public function testUpdateTokenWrongUser(): void { diff --git a/apps/settings/tests/Middleware/SubadminMiddlewareTest.php b/apps/settings/tests/Middleware/SubadminMiddlewareTest.php index 371af67734025..12fb31ce0c1b0 100644 --- a/apps/settings/tests/Middleware/SubadminMiddlewareTest.php +++ b/apps/settings/tests/Middleware/SubadminMiddlewareTest.php @@ -15,6 +15,7 @@ use OCA\Settings\Middleware\SubadminMiddleware; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\TemplateResponse; use OCP\Group\ISubAdmin; use OCP\IL10N; @@ -24,7 +25,7 @@ /** * Verifies whether an user has at least subadmin rights. - * To bypass use the `@NoSubAdminRequired` annotation + * To bypass use the `#[NoSubAdminRequired]` attribute * * @package Tests\Settings\Middleware */ @@ -62,15 +63,10 @@ public function testBeforeControllerAsUserWithoutAnnotation(): void { $this->expectException(NotAdminException::class); $this->reflector - ->expects($this->exactly(1)) - ->method('hasAnnotation') - ->willReturnMap([ - ['NoSubAdminRequired', false], - ]); - $this->reflector - ->expects($this->exactly(1)) + ->expects($this->exactly(2)) ->method('hasAnnotationOrAttribute') ->willReturnMap([ + ['NoSubAdminRequired', NoSubAdminRequired::class, false], ['AuthorizedAdminSetting', AuthorizedAdminSetting::class, false], ]); @@ -85,8 +81,8 @@ public function testBeforeControllerAsUserWithoutAnnotation(): void { public function testBeforeControllerWithAnnotation(): void { $this->reflector ->expects($this->once()) - ->method('hasAnnotation') - ->with('NoSubAdminRequired') + ->method('hasAnnotationOrAttribute') + ->with('NoSubAdminRequired', NoSubAdminRequired::class) ->willReturn(true); $this->subAdminManager @@ -98,15 +94,10 @@ public function testBeforeControllerWithAnnotation(): void { public function testBeforeControllerAsSubAdminWithoutAnnotation(): void { $this->reflector - ->expects($this->exactly(1)) - ->method('hasAnnotation') - ->willReturnMap([ - ['NoSubAdminRequired', false], - ]); - $this->reflector - ->expects($this->exactly(1)) + ->expects($this->exactly(2)) ->method('hasAnnotationOrAttribute') ->willReturnMap([ + ['NoSubAdminRequired', NoSubAdminRequired::class, false], ['AuthorizedAdminSetting', AuthorizedAdminSetting::class, false], ]); diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 89b92f79f339b..80c812273eb5a 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -2272,7 +2272,6 @@ - @@ -2289,14 +2288,6 @@ - - - - - - - - diff --git a/config/config.sample.php b/config/config.sample.php index cadfa4821dc16..b5a0faeca8a12 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -2511,9 +2511,9 @@ * Changing this may cause older, unsupported clients to malfunction, potentially * leading to data loss or unexpected behavior. * - * Defaults to ``3.2.82`` + * Defaults to ``3.3.50`` */ - 'minimum.supported.desktop.version' => '3.2.82', + 'minimum.supported.desktop.version' => '3.3.50', /** * Specify the maximum Nextcloud desktop client version allowed to sync with this diff --git a/core/Command/User/Add.php b/core/Command/User/Add.php index abc9c0bf4694a..093d35eef7d55 100644 --- a/core/Command/User/Add.php +++ b/core/Command/User/Add.php @@ -183,7 +183,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $user->setSystemEMailAddress($email); - if ($this->appConfig->getValueString('core', 'newUser.sendEmail', 'yes') === 'yes') { + if ($this->appConfig->getValueBool('core', 'newUser.sendEmail', true)) { try { $this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true)); $output->writeln('Welcome email sent to ' . $email); diff --git a/core/Controller/ProfileApiController.php b/core/Controller/ProfileApiController.php index 02979cb164979..97c5682b1b70e 100644 --- a/core/Controller/ProfileApiController.php +++ b/core/Controller/ProfileApiController.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoSubAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataResponse; @@ -49,8 +50,6 @@ public function __construct( } /** - * @NoSubAdminRequired - * * Update the visibility of a parameter * * @param string $targetUserId ID of the user @@ -63,6 +62,7 @@ public function __construct( * * 200: Visibility updated successfully */ + #[NoSubAdminRequired] #[NoAdminRequired] #[PasswordConfirmationRequired] #[UserRateLimit(limit: 40, period: 600)] diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 7154b071f242f..7f6c8757b0d16 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -129,6 +129,7 @@ 'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoSameSiteCookieRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoSameSiteCookieRequired.php', + 'OCP\\AppFramework\\Http\\Attribute\\NoSubAdminRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoSubAdminRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoTwoFactorRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoTwoFactorRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => $baseDir . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php', 'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php', @@ -1177,7 +1178,6 @@ 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => $baseDir . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', - 'OC\\AppFramework\\Middleware\\MiddlewareUtils' => $baseDir . '/lib/private/AppFramework/Middleware/MiddlewareUtils.php', 'OC\\AppFramework\\Middleware\\NotModifiedMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php', 'OC\\AppFramework\\Middleware\\OCSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index a05e8f8a3c1dd..3d2026f49ff4c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -170,6 +170,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoSameSiteCookieRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoSameSiteCookieRequired.php', + 'OCP\\AppFramework\\Http\\Attribute\\NoSubAdminRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoSubAdminRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\NoTwoFactorRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoTwoFactorRequired.php', 'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php', 'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php', @@ -1218,7 +1219,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', - 'OC\\AppFramework\\Middleware\\MiddlewareUtils' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/MiddlewareUtils.php', 'OC\\AppFramework\\Middleware\\NotModifiedMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php', 'OC\\AppFramework\\Middleware\\OCSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 15a1aff97ea66..28580065dfdde 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -17,7 +17,6 @@ use OC\AppFramework\Middleware\AdditionalScriptsMiddleware; use OC\AppFramework\Middleware\CompressionMiddleware; use OC\AppFramework\Middleware\MiddlewareDispatcher; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\NotModifiedMiddleware; use OC\AppFramework\Middleware\OCSMiddleware; use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware; @@ -205,7 +204,7 @@ public function __construct( $securityMiddleware = new SecurityMiddleware( $c->get(IRequest::class), - $c->get(MiddlewareUtils::class), + $c->get(ControllerMethodReflector::class), $c->get(INavigationManager::class), $c->get(IURLGenerator::class), $c->get(LoggerInterface::class), diff --git a/lib/private/AppFramework/Middleware/MiddlewareUtils.php b/lib/private/AppFramework/Middleware/MiddlewareUtils.php deleted file mode 100644 index 020d259e6322e..0000000000000 --- a/lib/private/AppFramework/Middleware/MiddlewareUtils.php +++ /dev/null @@ -1,60 +0,0 @@ - $attributeClass - * @deprecated 34.0.0 call directly on the reflector - */ - public function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, ?string $annotationName, string $attributeClass): bool { - return $this->reflector->hasAnnotationOrAttribute($annotationName, $attributeClass); - } - - /** - * @param ReflectionMethod $reflectionMethod - * @return string[] - */ - public function getAuthorizedAdminSettingClasses(ReflectionMethod $reflectionMethod): array { - $classes = []; - if ($this->reflector->hasAnnotation('AuthorizedAdminSetting')) { - $classes = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings')); - } - - $attributes = $reflectionMethod->getAttributes(AuthorizedAdminSetting::class); - if (!empty($attributes)) { - foreach ($attributes as $attribute) { - /** @var AuthorizedAdminSetting $setting */ - $setting = $attribute->newInstance(); - $classes[] = $setting->getSettings(); - } - } - - return $classes; - } -} diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index a894fe9a4a1e3..9fbdf5ffc5c33 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -8,8 +8,8 @@ namespace OC\AppFramework\Middleware\Security; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; +use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\User\Session; use OCP\AppFramework\Controller; @@ -35,7 +35,7 @@ class CORSMiddleware extends Middleware { public function __construct( private readonly IRequest $request, - private readonly MiddlewareUtils $middlewareUtils, + private readonly ControllerMethodReflector $reflector, private readonly Session $session, private readonly IThrottler $throttler, ) { @@ -43,12 +43,10 @@ public function __construct( #[Override] public function beforeController(Controller $controller, string $methodName): void { - $reflectionMethod = new ReflectionMethod($controller, $methodName); - // ensure that @CORS annotated API routes are not used in conjunction // with session authentication since this enables CSRF attack vectors - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) - && (!$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { + if ($this->reflector->hasAnnotationOrAttribute('CORS', CORS::class) + && (!$this->reflector->hasAnnotationOrAttribute('PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { $user = array_key_exists('PHP_AUTH_USER', $this->request->server) ? $this->request->server['PHP_AUTH_USER'] : null; $pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null; @@ -77,7 +75,7 @@ public function afterController(Controller $controller, string $methodName, Resp if (isset($this->request->server['HTTP_ORIGIN'])) { $reflectionMethod = new ReflectionMethod($controller, $methodName); - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class)) { + if ($this->reflector->hasAnnotationOrAttribute('CORS', CORS::class)) { // allow credentials headers must not be true or CSRF is possible // otherwise foreach ($response->getHeaders() as $header => $value) { diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index d6420607ee6b7..a1b5cc299b010 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -10,19 +10,18 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Http\Request; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\Exceptions\LaxSameSiteCookieFailedException; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoSameSiteCookieRequired; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; -use ReflectionMethod; class SameSiteCookieMiddleware extends Middleware { public function __construct( private readonly Request $request, - private readonly MiddlewareUtils $middlewareUtils, + private readonly ControllerMethodReflector $reflector, ) { } @@ -36,8 +35,7 @@ public function beforeController(Controller $controller, string $methodName): vo return; } - $reflectionMethod = new ReflectionMethod($controller, $methodName); - $noSSC = $this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoSameSiteCookieRequired', NoSameSiteCookieRequired::class); + $noSSC = $this->reflector->hasAnnotationOrAttribute('NoSameSiteCookieRequired', NoSameSiteCookieRequired::class); if ($noSSC) { return; } diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 5456d2ab940cc..1728d325e54b5 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -9,7 +9,6 @@ namespace OC\AppFramework\Middleware\Security; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\Exceptions\AdminIpNotAllowedException; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException; @@ -19,6 +18,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; +use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSRF\CsrfTokenManager; use OC\Settings\AuthorizedGroupMapper; use OC\User\Session; @@ -64,7 +64,7 @@ class SecurityMiddleware extends Middleware { public function __construct( private readonly IRequest $request, - private readonly MiddlewareUtils $middlewareUtils, + private readonly ControllerMethodReflector $reflector, private readonly INavigationManager $navigationManager, private readonly IURLGenerator $urlGenerator, private readonly LoggerInterface $logger, @@ -118,18 +118,16 @@ public function beforeController(Controller $controller, string $methodName): vo $this->navigationManager->setActiveEntry('spreed'); } - $reflectionMethod = new ReflectionMethod($controller, $methodName); - // security checks - $isPublicPage = $this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class); + $isPublicPage = $this->reflector->hasAnnotationOrAttribute('PublicPage', PublicPage::class); - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'ExAppRequired', ExAppRequired::class)) { + if ($this->reflector->hasAnnotationOrAttribute('ExAppRequired', ExAppRequired::class)) { if (!$this->userSession instanceof Session || $this->userSession->getSession()->get('app_api') !== true) { throw new ExAppRequiredException(); } } elseif (!$isPublicPage) { $authorized = false; - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, null, AppApiAdminAccessWithoutUser::class)) { + if ($this->reflector->hasAnnotationOrAttribute(null, AppApiAdminAccessWithoutUser::class)) { // this attribute allows ExApp to access admin endpoints only if "userId" is "null" if ($this->userSession instanceof Session && $this->userSession->getSession()->get('app_api') === true && $this->userSession->getUser() === null) { $authorized = true; @@ -140,15 +138,15 @@ public function beforeController(Controller $controller, string $methodName): vo throw new NotLoggedInException(); } - if (!$authorized && $this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { + if (!$authorized && $this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) { $authorized = $this->isAdminUser(); - if (!$authorized && $this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)) { + if (!$authorized && $this->reflector->hasAnnotationOrAttribute('SubAdminRequired', SubAdminRequired::class)) { $authorized = $this->isSubAdmin(); } if (!$authorized) { - $settingClasses = $this->middlewareUtils->getAuthorizedAdminSettingClasses($reflectionMethod); + $settingClasses = $this->getAuthorizedAdminSettingClasses(); $authorizedClasses = $this->groupAuthorizationMapper->findAllClassesForUser($this->userSession->getUser()); foreach ($settingClasses as $settingClass) { $authorized = in_array($settingClass, $authorizedClasses, true); @@ -165,24 +163,24 @@ public function beforeController(Controller $controller, string $methodName): vo throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } } - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) + if ($this->reflector->hasAnnotationOrAttribute('SubAdminRequired', SubAdminRequired::class) && !$this->isSubAdmin() && !$this->isAdminUser() && !$authorized) { throw new NotAdminException($this->l10n->t('Logged in account must be an admin or sub admin')); } - if (!$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) - && !$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class) + if (!$this->reflector->hasAnnotationOrAttribute('SubAdminRequired', SubAdminRequired::class) + && !$this->reflector->hasAnnotationOrAttribute('NoAdminRequired', NoAdminRequired::class) && !$this->isAdminUser() && !$authorized) { throw new NotAdminException($this->l10n->t('Logged in account must be an admin')); } - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) + if ($this->reflector->hasAnnotationOrAttribute('SubAdminRequired', SubAdminRequired::class) && !$this->remoteAddress->allowsAdminActions()) { throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } - if (!$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) - && !$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class) + if (!$this->reflector->hasAnnotationOrAttribute('SubAdminRequired', SubAdminRequired::class) + && !$this->reflector->hasAnnotationOrAttribute('NoAdminRequired', NoAdminRequired::class) && !$this->remoteAddress->allowsAdminActions()) { throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } @@ -190,15 +188,15 @@ public function beforeController(Controller $controller, string $methodName): vo } // Check for strict cookie requirement - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) - || !$this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { + if ($this->reflector->hasAnnotationOrAttribute('StrictCookieRequired', StrictCookiesRequired::class) + || !$this->reflector->hasAnnotationOrAttribute('NoCSRFRequired', NoCSRFRequired::class)) { if (!$this->request->passesStrictCookieCheck()) { throw new StrictCookieMissingException(); } } // CSRF check - also registers the CSRF token since the session may be closed later Server::get(CsrfTokenManager::class)->generateSessionToken(); - if ($this->isInvalidCSRFRequired($reflectionMethod)) { + if ($this->isInvalidCSRFRequired()) { /* * Only allow the CSRF check to fail on OCS Requests. This kind of * hacks around that we have no full token auth in place yet and we @@ -229,8 +227,8 @@ public function beforeController(Controller $controller, string $methodName): vo } } - private function isInvalidCSRFRequired(ReflectionMethod $reflectionMethod): bool { - if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { + private function isInvalidCSRFRequired(): bool { + if ($this->reflector->hasAnnotationOrAttribute('NoCSRFRequired', NoCSRFRequired::class)) { return false; } @@ -296,4 +294,24 @@ public function afterException(Controller $controller, string $methodName, \Exce throw $exception; } + + /** + * @param ReflectionMethod $reflectionMethod + * @return list + */ + public function getAuthorizedAdminSettingClasses(): array { + $classes = []; + if ($this->reflector->hasAnnotation('AuthorizedAdminSetting')) { + $classes = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings')); + } + + $attribute = $this->reflector->getAttribute(AuthorizedAdminSetting::class); + if ($attribute !== null) { + /** @var AuthorizedAdminSetting $setting */ + $setting = $attribute->newInstance(); + $classes[] = $setting->getSettings(); + } + + return $classes; + } } diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index b064dfc2bb38b..e09756a957bda 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -147,6 +147,19 @@ public function hasAnnotationOrAttribute(?string $annotationName, string $attrib return false; } + /** + * @template T + * @param class-string $attributeClass + * @return ?\ReflectionAttribute + */ + public function getAttribute(string $attributeClass): ?\ReflectionAttribute { + $attributes = $this->reflectionMethod->getAttributes($attributeClass); + if (!empty($attributes)) { + return $attributes[0]; + } + return null; + } + /** * Check if a method contains an annotation * @param string $name the name of the annotation diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 9039c2317394a..04a7f4e84fd44 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -1042,7 +1042,7 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i $id = $entry['fileid']; $query = $this->getQueryBuilder(); - $query->select('size', 'unencrypted_size') + $query->select('size', 'unencrypted_size', 'encrypted') ->from('filecache') ->whereStorageId($this->getNumericStorageId()) ->whereParent($id); @@ -1062,7 +1062,7 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i return Util::numericToNumber($row['unencrypted_size']); }, $rows); $unencryptedSizes = array_map(function (array $row) { - return Util::numericToNumber(($row['unencrypted_size'] > 0) ? $row['unencrypted_size'] : $row['size']); + return Util::numericToNumber($row['encrypted'] ? $row['unencrypted_size'] : $row['size']); }, $rows); $sum = array_sum($sizes); diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index 1dbdda4240b6b..c2e8af9caa305 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -137,7 +137,7 @@ public function __clone() { #[\Override] public function getUnencryptedSize(): int { - if ($this->data['encrypted'] && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + if ($this->data['encrypted'] && isset($this->data['unencrypted_size'])) { return $this->data['unencrypted_size']; } else { return $this->data['size'] ?? 0; diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index b965a578a6337..0b71ef3539e3b 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -184,8 +184,10 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = } } - // we only updated unencrypted_size if it's already set - if (isset($cacheData['unencrypted_size']) && $cacheData['unencrypted_size'] === 0) { + // Skip updating unencrypted_size only when both cached and new values are 0 + if (isset($cacheData['unencrypted_size']) + && $cacheData['unencrypted_size'] === 0 + && isset($data['unencrypted_size']) && $data['unencrypted_size'] === 0) { unset($data['unencrypted_size']); } @@ -203,7 +205,10 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = $data['etag_changed'] = true; } } else { - unset($data['unencrypted_size']); + // For new files, only preserve unencrypted_size when the file is encrypted + if (!isset($data['encrypted']) || !$data['encrypted']) { + unset($data['unencrypted_size']); + } $newData = $data; $fileId = -1; } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 86d92461e1ba8..a4323834b2467 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -59,7 +59,7 @@ public function __construct( private ?IUser $owner = null, ) { $this->mount = $mount; - if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] !== 0) { + if (($this->data['encrypted'] ?? false) && isset($this->data['unencrypted_size'])) { $this->rawSize = $this->data['unencrypted_size']; } else { $this->rawSize = $this->data['size'] ?? 0; @@ -174,7 +174,7 @@ public function getSize($includeMounts = true) { if ($includeMounts) { $this->updateEntryFromSubMounts(); - if ($this->isEncrypted() && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + if ($this->isEncrypted() && isset($this->data['unencrypted_size'])) { return $this->data['unencrypted_size']; } else { return isset($this->data['size']) ? 0 + $this->data['size'] : 0; @@ -356,7 +356,7 @@ public function addSubEntry($data, $entryPath) { if (!$data) { return; } - $hasUnencryptedSize = isset($data['unencrypted_size']) && $data['unencrypted_size'] > 0; + $hasUnencryptedSize = !empty($data['encrypted']) && isset($data['unencrypted_size']); if ($hasUnencryptedSize) { $subSize = $data['unencrypted_size']; } else { diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index e38485f755482..35942816a8f69 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -355,18 +355,20 @@ public function rename(string $source, string $target): bool { $srcParent = dirname($source); $dstParent = dirname($target); + $logger = Server::get(LoggerInterface::class); + if (!$this->isUpdatable($srcParent)) { - Server::get(LoggerInterface::class)->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']); + $logger->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']); return false; } if (!$this->isUpdatable($dstParent)) { - Server::get(LoggerInterface::class)->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']); + $logger->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']); return false; } if (!$this->file_exists($source)) { - Server::get(LoggerInterface::class)->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']); + $logger->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']); return false; } @@ -378,20 +380,43 @@ public function rename(string $source, string $target): bool { } } + $absoluteSource = $this->getSourcePath($source); + $absoluteTarget = $this->getSourcePath($target); + if ($this->is_dir($source)) { - $this->checkTreeForForbiddenItems($this->getSourcePath($source)); + $this->checkTreeForForbiddenItems($absoluteSource); } - if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) { + if (@rename($absoluteSource, $absoluteTarget)) { if ($this->caseInsensitive) { if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) { return false; } } return true; + } else { + $logger->error('failed to rename ' . $absoluteSource . ' to ' . $absoluteTarget . ', trying copy+delete fallback instead', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); } - return $this->copy($source, $target) && $this->unlink($source); + if (!$this->copy($source, $target)) { + $logger->error('failed to copy ' . $absoluteSource . ' to ' . $absoluteTarget . ' as part of rename fallback', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); + return false; + } + + if (!$this->unlink($source)) { + $logger->error('failed to delete ' . $absoluteSource . ' as part of rename fallback', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); + return false; + } + return true; } #[\Override] diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 20af8b1d06277..4a1fa9135a46d 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -399,6 +399,7 @@ protected function verifyUnencryptedSize(string $path, int $unencryptedSize): in if ($unencryptedSize < 0 || ($size > 0 && $unencryptedSize === $size) || $unencryptedSize > $size + || ($unencryptedSize === 0 && $size > $this->util->getHeaderSize()) ) { // check if we already calculate the unencrypted size for the // given path to avoid recursions diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index 80ad76c072492..d3ab54d750bdc 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -29,7 +29,7 @@ class Encryption extends Wrapper { protected string $cache; protected ?int $size = null; protected int $position; - protected ?int $unencryptedSize = null; + protected int|float|null $unencryptedSize = null; protected int $headerSize; protected int $unencryptedBlockSize; protected array $header; diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index ec048a2d08a89..f8bb84d58bbbf 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -163,6 +163,10 @@ public function get($uid) { #[\Override] public function getDisplayName(string $uid): ?string { + if (isset($this->cachedUsers[$uid])) { + return $this->cachedUsers[$uid]->getDisplayName(); + } + return $this->displayNameCache->getDisplayName($uid); } diff --git a/lib/public/AppFramework/Http/Attribute/NoSubAdminRequired.php b/lib/public/AppFramework/Http/Attribute/NoSubAdminRequired.php new file mode 100644 index 0000000000000..ea5bc047ce267 --- /dev/null +++ b/lib/public/AppFramework/Http/Attribute/NoSubAdminRequired.php @@ -0,0 +1,21 @@ +userManager->method('createUser') ->willReturn($this->user); - $this->appConfig->method('getValueString') - ->willReturn($shouldSendEmail ? 'yes' : 'no'); + $this->appConfig->method('getValueBool') + ->willReturn($shouldSendEmail); $this->mailHelper->method('generateTemplate') ->willReturn(static::createMock(IEMailTemplate::class)); diff --git a/tests/lib/AppConfigIntegrationTest.php b/tests/lib/AppConfigIntegrationTest.php index 7d909fd4a141e..8a65f8fc418d9 100644 --- a/tests/lib/AppConfigIntegrationTest.php +++ b/tests/lib/AppConfigIntegrationTest.php @@ -494,6 +494,34 @@ public function testGetValueBool(): void { $this->assertSame(true, $config->getValueBool('typed', 'bool')); } + /** + * Untyped values predate the typed config API and are still in the database of + * every upgraded instance, so they have to keep resolving to a boolean. The + * deprecated setter is used on purpose, as it is the only way to write a value + * without a type. + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataUntypedBool')] + public function testGetValueBoolOnUntypedValue(string $stored, bool $expected): void { + /** @var AppConfig $config */ + $config = $this->generateAppConfig(); + $config->setValue('feed', 'untyped-bool', $stored); + + $this->assertSame($expected, $config->getValueBool('feed', 'untyped-bool')); + } + + public static function dataUntypedBool(): array { + return [ + 'yes' => ['yes', true], + 'no' => ['no', false], + 'true' => ['true', true], + 'false' => ['false', false], + 'on' => ['on', true], + '1' => ['1', true], + '0' => ['0', false], + 'empty' => ['', false], + ]; + } + public function testGetValueBoolOnUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->getValueBool('typed-1', 'bool', false)); diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php index 702b0c3260775..5695923ac3dc4 100644 --- a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php @@ -9,7 +9,6 @@ namespace Test\AppFramework\Middleware\Security; use OC\AppFramework\Http\Request; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\CORSMiddleware; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\AppFramework\Utility\ControllerMethodReflector; @@ -65,7 +64,7 @@ public function testSetCORSAPIHeader(string $method): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $response = $middleware->afterController($this->controller, $method, new Response()); $headers = $response->getHeaders(); @@ -83,7 +82,7 @@ public function testNoAnnotationNoCORSHEADER(): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, __FUNCTION__); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $response = $middleware->afterController($this->controller, __FUNCTION__, new Response()); $headers = $response->getHeaders(); @@ -105,7 +104,7 @@ public function testNoOriginHeaderNoCORSHEADER(string $method): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $response = $middleware->afterController($this->controller, $method, new Response()); $headers = $response->getHeaders(); @@ -133,7 +132,7 @@ public function testCorsIgnoredIfWithCredentialsHeaderPresent(string $method): v $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); $response = new Response(); $response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE'); @@ -157,7 +156,7 @@ public function testNoCORSOnAnonymousPublicPage(string $method): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); $this->session->expects($this->once()) ->method('isLoggedIn') ->willReturn(false); @@ -189,7 +188,7 @@ public function testCORSShouldNeverAllowCookieAuth(string $method): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $this->session->expects($this->once()) ->method('isLoggedIn') ->willReturn(true); @@ -228,7 +227,7 @@ public function testCORSShouldRelogin(string $method): void { ->with($this->equalTo('user'), $this->equalTo('pass')) ->willReturn(true); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $middleware->beforeController($this->controller, $method); } @@ -259,7 +258,7 @@ public function testCORSShouldFailIfPasswordLoginIsForbidden(string $method): vo ->with($this->equalTo('user'), $this->equalTo('pass')) ->willThrowException(new PasswordLoginForbiddenException); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $middleware->beforeController($this->controller, $method); } @@ -290,7 +289,7 @@ public function testCORSShouldNotAllowCookieAuth(string $method): void { ->with($this->equalTo('user'), $this->equalTo('pass')) ->willReturn(false); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $middleware->beforeController($this->controller, $method); } @@ -305,7 +304,7 @@ public function testAfterExceptionWithSecurityExceptionNoStatus(): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, __FUNCTION__); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception')); $expected = new JSONResponse(['message' => 'A security exception'], 500); @@ -322,7 +321,7 @@ public function testAfterExceptionWithSecurityExceptionWithStatus(): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, __FUNCTION__); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception', 501)); $expected = new JSONResponse(['message' => 'A security exception'], 501); @@ -342,7 +341,7 @@ public function testAfterExceptionWithRegularException(): void { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, __FUNCTION__); - $middleware = new CORSMiddleware($request, new MiddlewareUtils($this->reflector, $this->logger), $this->session, $this->throttler); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler); $middleware->afterException($this->controller, __FUNCTION__, new \Exception('A regular exception')); } } diff --git a/tests/lib/AppFramework/Middleware/Security/SameSiteCookieMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SameSiteCookieMiddlewareTest.php index f7ec3a94a6ed3..08691f6b4b27c 100644 --- a/tests/lib/AppFramework/Middleware/Security/SameSiteCookieMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SameSiteCookieMiddlewareTest.php @@ -8,7 +8,6 @@ namespace Test\AppFramework\Middleware\Security; use OC\AppFramework\Http\Request; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\Exceptions\LaxSameSiteCookieFailedException; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware; @@ -47,7 +46,7 @@ protected function setUp(): void { $this->request = $this->createMock(Request::class); $this->logger = $this->createMock(LoggerInterface::class); $this->reflector = $this->createMock(ControllerMethodReflector::class); - $this->middleware = new SameSiteCookieMiddleware($this->request, new MiddlewareUtils($this->reflector, $this->logger)); + $this->middleware = new SameSiteCookieMiddleware($this->request, $this->reflector); } #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] @@ -120,7 +119,7 @@ public function testAfterExceptionLaxCookie(): void { ->willReturn('/myrequri'); $middleware = $this->getMockBuilder(SameSiteCookieMiddleware::class) - ->setConstructorArgs([$this->request, new MiddlewareUtils($this->reflector, $this->logger)]) + ->setConstructorArgs([$this->request, $this->reflector]) ->onlyMethods(['setSameSiteCookie']) ->getMock(); diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index c34248411fb92..1f2a092de64da 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -10,7 +10,6 @@ use OC\AppFramework\Http; use OC\AppFramework\Http\Request; -use OC\AppFramework\Middleware\MiddlewareUtils; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException; use OC\AppFramework\Middleware\Security\Exceptions\ExAppRequiredException; @@ -51,7 +50,6 @@ class SecurityMiddlewareTest extends \Test\TestCase { private SecurityMiddlewareController $controller; private SecurityException $secAjaxException; private IRequest|MockObject $request; - private MiddlewareUtils $middlewareUtils; private LoggerInterface&MockObject $logger; private INavigationManager&MockObject $navigationManager; private IURLGenerator&MockObject $urlGenerator; @@ -79,7 +77,6 @@ protected function setUp(): void { $this->navigationManager = $this->createMock(INavigationManager::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->l10n = $this->createMock(IL10N::class); - $this->middlewareUtils = new MiddlewareUtils($this->reader, $this->logger); $this->middleware = $this->getMiddleware(true, true, false); $this->secAjaxException = new SecurityException('hey', true); } @@ -101,7 +98,7 @@ private function getMiddleware(bool $isLoggedIn, bool $isAdminUser, bool $isSubA return new SecurityMiddleware( $this->request, - $this->middlewareUtils, + $this->reader, $this->navigationManager, $this->urlGenerator, $this->logger, diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php index cca9b6b2a174e..c072250a5ab1b 100644 --- a/tests/lib/Files/Cache/CacheTest.php +++ b/tests/lib/Files/Cache/CacheTest.php @@ -153,6 +153,16 @@ public function testCacheEntryGetters(): void { $this->assertEquals($entry->getUnencryptedSize(), 100); } + public function testGetUnencryptedSizeEncryptedZeroByte(): void { + $file1 = 'encrypted_zero'; + $this->cache->put($file1, ['size' => 8192, 'mtime' => 50, 'mimetype' => 'application/octet-stream', 'encrypted' => 1, 'unencrypted_size' => 0]); + $entry = $this->cache->get($file1); + + // getUnencryptedSize() must return 0 (the true plaintext size), not 8192 (the encrypted on-disk size) + $this->assertEquals(0, $entry->getUnencryptedSize()); + $this->assertTrue($entry->isEncrypted()); + } + public function testPartial(): void { $file1 = 'foo'; @@ -292,6 +302,39 @@ public function testEncryptedFolder(): void { $this->assertFalse($this->cache->inCache('folder/bar')); } + public function testCalculateFolderSizeWithEncryptedZeroByte(): void { + $folder = 'enc_folder'; + $this->cache->put($folder, ['size' => -1, 'mtime' => 20, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + + // Child 1: zero-byte encrypted file — on-disk 8192 (header only), plaintext 0 + $child1 = $folder . '/empty.enc'; + $this->cache->put($child1, [ + 'size' => 8192, + 'mtime' => 20, + 'mimetype' => 'application/octet-stream', + 'encrypted' => 1, + 'unencrypted_size' => 0, + ]); + + // Child 2: non-zero encrypted file — opens the write-back gate ($unencryptedMax > 0) + $child2 = $folder . '/small.enc'; + $this->cache->put($child2, [ + 'size' => 8292, + 'mtime' => 20, + 'mimetype' => 'application/octet-stream', + 'encrypted' => 1, + 'unencrypted_size' => 100, + ]); + + $this->cache->calculateFolderSize($folder); + + $entry = $this->cache->get($folder); + // Must sum plaintext sizes (0 + 100 = 100), not fall back to on-disk size for + // the zero-byte child (8192 + 100 = 8292 with the old buggy code) + $this->assertEquals(100, $entry['unencrypted_size'], 'Folder unencrypted_size should sum plaintext sizes'); + $this->assertEquals(16484, $entry['size'], 'Folder size should sum on-disk sizes (8192 + 8292)'); + } + public function testRootFolderSizeForNonHomeStorage(): void { $dir1 = 'knownsize'; $dir2 = 'unknownsize'; diff --git a/tests/lib/Files/FileInfoTest.php b/tests/lib/Files/FileInfoTest.php index 733f77fed1423..da80520905648 100644 --- a/tests/lib/Files/FileInfoTest.php +++ b/tests/lib/Files/FileInfoTest.php @@ -29,6 +29,30 @@ protected function setUp(): void { $this->config = $this->getMockBuilder(IConfig::class)->getMock(); } + private function makeFileInfo(array $data): FileInfo { + $storage = new Temporary(); + return new FileInfo('', $storage, '', $data, new MountPoint($storage, '/foo/files')); + } + + public function testGetSizeEncryptedZeroByte(): void { + $info = $this->makeFileInfo(['encrypted' => true, 'size' => 8192, 'unencrypted_size' => 0]); + // Both paths must report the true plaintext size (0), not the on-disk encrypted size (8192) + $this->assertSame(0, $info->getSize(true)); + $this->assertSame(0, $info->getSize(false)); + } + + public function testGetSizeEncryptedNonZero(): void { + $info = $this->makeFileInfo(['encrypted' => true, 'size' => 16384, 'unencrypted_size' => 5000]); + $this->assertSame(5000, $info->getSize(true)); + $this->assertSame(5000, $info->getSize(false)); + } + + public function testGetSizeNonEncrypted(): void { + $info = $this->makeFileInfo(['encrypted' => false, 'size' => 100]); + $this->assertSame(100, $info->getSize(true)); + $this->assertSame(100, $info->getSize(false)); + } + public function testIsMountedHomeStorage(): void { $user = $this->createMock(IUser::class); $user->method('getUID') diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php index 79392780da288..5f7e157d67cf0 100644 --- a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -386,7 +386,11 @@ public static function dataTestVerifyUnencryptedSize(): array { [120, 80, false, 80], [120, 120, false, 80], [120, -1, false, 80], - [120, -1, true, -1] + [120, -1, true, -1], + // Zero-byte encrypted file: on-disk size equals header only (8192) — should NOT recalculate + [8192, 0, false, 0], + // Encrypted file with content beyond header but unencrypted_size=0 — SHOULD recalculate + [16384, 0, false, 80], ]; }