aboutsummaryrefslogtreecommitdiffstats
path: root/build/integration
diff options
context:
space:
mode:
Diffstat (limited to 'build/integration')
-rw-r--r--build/integration/composer.lock468
-rw-r--r--build/integration/config/behat.yml75
-rw-r--r--build/integration/data/clouds.jpgbin0 -> 538205 bytes
-rw-r--r--build/integration/data/clouds.jpg.license2
-rw-r--r--build/integration/dav_features/caldav.feature5
-rw-r--r--build/integration/dav_features/carddav.feature6
-rw-r--r--build/integration/dav_features/dav-v2.feature80
-rw-r--r--build/integration/dav_features/principal-property-search.feature13
-rw-r--r--build/integration/dav_features/webdav-related.feature40
-rw-r--r--build/integration/features/bootstrap/Activity.php32
-rw-r--r--build/integration/features/bootstrap/AppConfiguration.php1
-rw-r--r--build/integration/features/bootstrap/Avatar.php13
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php15
-rw-r--r--build/integration/features/bootstrap/CalDavContext.php1
-rw-r--r--build/integration/features/bootstrap/CapabilitiesContext.php1
-rw-r--r--build/integration/features/bootstrap/CardDavContext.php1
-rw-r--r--build/integration/features/bootstrap/ChecksumsContext.php1
-rw-r--r--build/integration/features/bootstrap/CommandLine.php1
-rw-r--r--build/integration/features/bootstrap/CommandLineContext.php14
-rw-r--r--build/integration/features/bootstrap/CommentsContext.php1
-rw-r--r--build/integration/features/bootstrap/ContactsMenu.php1
-rw-r--r--build/integration/features/bootstrap/ConversionsContext.php60
-rw-r--r--build/integration/features/bootstrap/DavFeatureContext.php1
-rw-r--r--build/integration/features/bootstrap/Download.php1
-rw-r--r--build/integration/features/bootstrap/ExternalStorage.php1
-rw-r--r--build/integration/features/bootstrap/FakeSMTPHelper.php1
-rw-r--r--build/integration/features/bootstrap/FeatureContext.php8
-rw-r--r--build/integration/features/bootstrap/FederationContext.php46
-rw-r--r--build/integration/features/bootstrap/FilesDropContext.php33
-rw-r--r--build/integration/features/bootstrap/LDAPContext.php1
-rw-r--r--build/integration/features/bootstrap/Mail.php1
-rw-r--r--build/integration/features/bootstrap/MetadataContext.php124
-rw-r--r--build/integration/features/bootstrap/PrincipalPropertySearchContext.php141
-rw-r--r--build/integration/features/bootstrap/Provisioning.php80
-rw-r--r--build/integration/features/bootstrap/RateLimitingContext.php31
-rw-r--r--build/integration/features/bootstrap/RemoteContext.php1
-rw-r--r--build/integration/features/bootstrap/RoutingContext.php19
-rw-r--r--build/integration/features/bootstrap/Search.php1
-rw-r--r--build/integration/features/bootstrap/SetupContext.php1
-rw-r--r--build/integration/features/bootstrap/ShareesContext.php1
-rw-r--r--build/integration/features/bootstrap/Sharing.php23
-rw-r--r--build/integration/features/bootstrap/SharingContext.php5
-rw-r--r--build/integration/features/bootstrap/TagsContext.php7
-rw-r--r--build/integration/features/bootstrap/TalkContext.php1
-rw-r--r--build/integration/features/bootstrap/Theming.php49
-rw-r--r--build/integration/features/bootstrap/Trashbin.php1
-rw-r--r--build/integration/features/bootstrap/WebDav.php7
-rw-r--r--build/integration/features/contacts-menu.feature30
-rw-r--r--build/integration/features/provisioning-v1.feature74
-rw-r--r--build/integration/federation_features/federated.feature219
-rw-r--r--build/integration/file_conversions/file_conversions.feature122
-rw-r--r--build/integration/files_features/metadata.feature16
-rw-r--r--build/integration/files_features/transfer-ownership.feature53
-rw-r--r--build/integration/files_features/windows_compatibility.feature68
-rw-r--r--build/integration/filesdrop_features/filesdrop.feature161
-rw-r--r--build/integration/openldap_features/openldap-uid-username.feature2
-rw-r--r--build/integration/ratelimiting_features/ratelimiting.feature (renamed from build/integration/features/ratelimiting.feature)1
-rw-r--r--build/integration/routing_features/apps-and-routes.feature52
-rwxr-xr-xbuild/integration/run.sh9
-rw-r--r--build/integration/sharing_features/sharing-activity.feature46
-rw-r--r--build/integration/sharing_features/sharing-v1-part2.feature130
-rw-r--r--build/integration/sharing_features/sharing-v1-part4.feature58
-rw-r--r--build/integration/sharing_features/sharing-v1.feature42
-rw-r--r--build/integration/theming_features/theming.feature131
64 files changed, 2205 insertions, 425 deletions
diff --git a/build/integration/composer.lock b/build/integration/composer.lock
index 64678c07151..cc8427c78eb 100644
--- a/build/integration/composer.lock
+++ b/build/integration/composer.lock
@@ -9,23 +9,24 @@
"packages-dev": [
{
"name": "behat/behat",
- "version": "v3.16.0",
+ "version": "v3.23.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "18f5f44c80d1cf5711ec27019afcb26b8bc6c3b0"
+ "reference": "c465af8756adaaa6d962c3176a0a6c594361809b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/18f5f44c80d1cf5711ec27019afcb26b8bc6c3b0",
- "reference": "18f5f44c80d1cf5711ec27019afcb26b8bc6c3b0",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/c465af8756adaaa6d962c3176a0a6c594361809b",
+ "reference": "c465af8756adaaa6d962c3176a0a6c594361809b",
"shasum": ""
},
"require": {
- "behat/gherkin": "^4.10.0",
- "behat/transliterator": "^1.5",
+ "behat/gherkin": "^4.12.0",
"composer-runtime-api": "^2.2",
+ "composer/xdebug-handler": "^1.4 || ^2.0 || ^3.0",
"ext-mbstring": "*",
+ "nikic/php-parser": "^4.19.2 || ^5.2",
"php": "8.1.* || 8.2.* || 8.3.* || 8.4.* ",
"psr/container": "^1.0 || ^2.0",
"symfony/config": "^5.4 || ^6.4 || ^7.0",
@@ -36,11 +37,13 @@
"symfony/yaml": "^5.4 || ^6.4 || ^7.0"
},
"require-dev": {
- "herrera-io/box": "~1.6.1",
+ "friendsofphp/php-cs-fixer": "^3.68",
+ "phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
+ "rector/rector": "^2.0",
+ "sebastian/diff": "^4.0",
"symfony/polyfill-php84": "^1.31",
- "symfony/process": "^5.4 || ^6.4 || ^7.0",
- "vimeo/psalm": "^4.8"
+ "symfony/process": "^5.4 || ^6.4 || ^7.0"
},
"suggest": {
"ext-dom": "Needed to output test results in JUnit format."
@@ -59,7 +62,9 @@
"Behat\\Hook\\": "src/Behat/Hook/",
"Behat\\Step\\": "src/Behat/Step/",
"Behat\\Behat\\": "src/Behat/Behat/",
- "Behat\\Testwork\\": "src/Behat/Testwork/"
+ "Behat\\Config\\": "src/Behat/Config/",
+ "Behat\\Testwork\\": "src/Behat/Testwork/",
+ "Behat\\Transformation\\": "src/Behat/Transformation/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -91,31 +96,37 @@
],
"support": {
"issues": "https://github.com/Behat/Behat/issues",
- "source": "https://github.com/Behat/Behat/tree/v3.16.0"
+ "source": "https://github.com/Behat/Behat/tree/v3.23.0"
},
- "time": "2024-11-08T12:25:17+00:00"
+ "time": "2025-07-15T16:58:54+00:00"
},
{
"name": "behat/gherkin",
- "version": "v4.10.0",
+ "version": "v4.14.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6"
+ "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6",
- "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4",
+ "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4",
"shasum": ""
},
"require": {
- "php": "~7.2|~8.0"
+ "composer-runtime-api": "^2.2",
+ "php": "8.1.* || 8.2.* || 8.3.* || 8.4.*"
},
"require-dev": {
- "cucumber/cucumber": "dev-gherkin-24.1.0",
- "phpunit/phpunit": "~8|~9",
- "symfony/yaml": "~3|~4|~5|~6|~7"
+ "cucumber/gherkin-monorepo": "dev-gherkin-v32.1.1",
+ "friendsofphp/php-cs-fixer": "^3.65",
+ "mikey179/vfsstream": "^1.6",
+ "phpstan/extension-installer": "^1",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-phpunit": "^2",
+ "phpunit/phpunit": "^10.5",
+ "symfony/yaml": "^5.4 || ^6.4 || ^7.0"
},
"suggest": {
"symfony/yaml": "If you want to parse features, represented in YAML files"
@@ -127,8 +138,8 @@
}
},
"autoload": {
- "psr-0": {
- "Behat\\Gherkin": "src/"
+ "psr-4": {
+ "Behat\\Gherkin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -139,11 +150,11 @@
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
+ "homepage": "https://everzet.com"
}
],
"description": "Gherkin DSL parser for PHP",
- "homepage": "http://behat.org/",
+ "homepage": "https://behat.org/",
"keywords": [
"BDD",
"Behat",
@@ -154,58 +165,154 @@
],
"support": {
"issues": "https://github.com/Behat/Gherkin/issues",
- "source": "https://github.com/Behat/Gherkin/tree/v4.10.0"
+ "source": "https://github.com/Behat/Gherkin/tree/v4.14.0"
},
- "time": "2024-10-19T14:46:06+00:00"
+ "time": "2025-05-23T15:06:40+00:00"
},
{
- "name": "behat/transliterator",
- "version": "v1.5.0",
+ "name": "composer/pcre",
+ "version": "3.3.2",
"source": {
"type": "git",
- "url": "https://github.com/Behat/Transliterator.git",
- "reference": "baac5873bac3749887d28ab68e2f74db3a4408af"
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af",
- "reference": "baac5873bac3749887d28ab68e2f74db3a4408af",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
- "php": ">=7.2"
+ "php": "^7.4 || ^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<1.11.10"
},
"require-dev": {
- "chuyskywalker/rolling-curl": "^3.1",
- "php-yaoi/php-yaoi": "^1.0",
- "phpunit/phpunit": "^8.5.25 || ^9.5.19"
+ "phpstan/phpstan": "^1.12 || ^2",
+ "phpstan/phpstan-strict-rules": "^1 || ^2",
+ "phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
"branch-alias": {
- "dev-master": "1.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
- "Behat\\Transliterator\\": "src/Behat/Transliterator"
+ "Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "Artistic-1.0"
+ "MIT"
],
- "description": "String transliterator",
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
- "i18n",
- "slug",
- "transliterator"
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
],
"support": {
- "issues": "https://github.com/Behat/Transliterator/issues",
- "source": "https://github.com/Behat/Transliterator/tree/v1.5.0"
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.3.2"
},
- "time": "2022-03-30T09:27:43+00:00"
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-12T16:29:46+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without Xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/xdebug-handler/issues",
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-06T16:37:16+00:00"
},
{
"name": "dms/phpunit-arraysubset-asserts",
@@ -323,16 +430,16 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.9.2",
+ "version": "7.9.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+ "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
- "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
+ "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"shasum": ""
},
"require": {
@@ -429,7 +536,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+ "source": "https://github.com/guzzle/guzzle/tree/7.9.3"
},
"funding": [
{
@@ -445,20 +552,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-24T11:22:20+00:00"
+ "time": "2025-03-27T13:37:11+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "2.0.4",
+ "version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
+ "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
- "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
+ "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"shasum": ""
},
"require": {
@@ -512,7 +619,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/2.0.4"
+ "source": "https://github.com/guzzle/promises/tree/2.2.0"
},
"funding": [
{
@@ -528,20 +635,20 @@
"type": "tidelift"
}
],
- "time": "2024-10-17T10:06:22+00:00"
+ "time": "2025-03-27T13:27:01+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "2.7.0",
+ "version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
- "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"shasum": ""
},
"require": {
@@ -628,7 +735,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.7.1"
},
"funding": [
{
@@ -644,7 +751,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-18T11:15:46+00:00"
+ "time": "2025-03-27T12:30:47+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -708,16 +815,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.3.1",
+ "version": "v5.5.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
- "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
"shasum": ""
},
"require": {
@@ -760,9 +867,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
},
- "time": "2024-10-08T18:51:32+00:00"
+ "time": "2025-05-31T08:24:38+00:00"
},
{
"name": "phar-io/manifest",
@@ -3071,16 +3178,16 @@
},
{
"name": "symfony/config",
- "version": "v6.4.14",
+ "version": "v6.4.22",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef"
+ "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef",
- "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef",
+ "url": "https://api.github.com/repos/symfony/config/zipball/af5917a3b1571f54689e56677a3f06440d2fe4c7",
+ "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7",
"shasum": ""
},
"require": {
@@ -3126,7 +3233,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v6.4.14"
+ "source": "https://github.com/symfony/config/tree/v6.4.22"
},
"funding": [
{
@@ -3142,20 +3249,20 @@
"type": "tidelift"
}
],
- "time": "2024-11-04T11:33:53+00:00"
+ "time": "2025-05-14T06:00:01+00:00"
},
{
"name": "symfony/console",
- "version": "v6.4.14",
+ "version": "v6.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b"
+ "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/897c2441ed4eec8a8a2c37b943427d24dba3f26b",
- "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b",
+ "url": "https://api.github.com/repos/symfony/console/zipball/9056771b8eca08d026cd3280deeec3cfd99c4d93",
+ "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93",
"shasum": ""
},
"require": {
@@ -3220,7 +3327,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.14"
+ "source": "https://github.com/symfony/console/tree/v6.4.23"
},
"funding": [
{
@@ -3236,20 +3343,20 @@
"type": "tidelift"
}
],
- "time": "2024-11-05T15:34:40+00:00"
+ "time": "2025-06-27T19:37:22+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.4.13",
+ "version": "v6.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96"
+ "reference": "0d9f24f3de0a83573fce5c9ed025d6306c6e166b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96",
- "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0d9f24f3de0a83573fce5c9ed025d6306c6e166b",
+ "reference": "0d9f24f3de0a83573fce5c9ed025d6306c6e166b",
"shasum": ""
},
"require": {
@@ -3257,7 +3364,7 @@
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/service-contracts": "^2.5|^3.0",
- "symfony/var-exporter": "^6.2.10|^7.0"
+ "symfony/var-exporter": "^6.4.20|^7.2.5"
},
"conflict": {
"ext-psr": "<1.1|>=2",
@@ -3301,7 +3408,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13"
+ "source": "https://github.com/symfony/dependency-injection/tree/v6.4.23"
},
"funding": [
{
@@ -3317,20 +3424,20 @@
"type": "tidelift"
}
],
- "time": "2024-10-25T15:07:50+00:00"
+ "time": "2025-06-23T06:49:06+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.5.0",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
- "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
@@ -3338,12 +3445,12 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.5-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -3368,7 +3475,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -3384,7 +3491,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:32:20+00:00"
+ "time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -3468,16 +3575,16 @@
},
{
"name": "symfony/event-dispatcher-contracts",
- "version": "v3.5.0",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
- "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50"
+ "reference": "59eb412e93815df44f05f342958efa9f46b1e586"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50",
- "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586",
+ "reference": "59eb412e93815df44f05f342958efa9f46b1e586",
"shasum": ""
},
"require": {
@@ -3486,12 +3593,12 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.5-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -3524,7 +3631,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0"
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -3540,7 +3647,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:32:20+00:00"
+ "time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/filesystem",
@@ -3610,7 +3717,7 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -3634,8 +3741,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -3669,7 +3776,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
},
"funding": [
{
@@ -3689,7 +3796,7 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
@@ -3710,8 +3817,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -3747,7 +3854,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0"
},
"funding": [
{
@@ -3767,7 +3874,7 @@
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -3788,8 +3895,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -3828,7 +3935,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0"
},
"funding": [
{
@@ -3848,19 +3955,20 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
+ "ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
@@ -3872,8 +3980,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -3908,7 +4016,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
},
"funding": [
{
@@ -3924,20 +4032,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.5.0",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
- "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
"shasum": ""
},
"require": {
@@ -3950,12 +4058,12 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.5-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -3991,7 +4099,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -4007,20 +4115,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:32:20+00:00"
+ "time": "2025-04-25T09:37:31+00:00"
},
{
"name": "symfony/string",
- "version": "v6.4.13",
+ "version": "v6.4.21",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627"
+ "reference": "73e2c6966a5aef1d4892873ed5322245295370c6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627",
- "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627",
+ "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6",
+ "reference": "73e2c6966a5aef1d4892873ed5322245295370c6",
"shasum": ""
},
"require": {
@@ -4077,7 +4185,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.4.13"
+ "source": "https://github.com/symfony/string/tree/v6.4.21"
},
"funding": [
{
@@ -4093,20 +4201,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:18:03+00:00"
+ "time": "2025-04-18T15:23:29+00:00"
},
{
"name": "symfony/translation",
- "version": "v6.4.13",
+ "version": "v6.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66"
+ "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66",
- "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/de8afa521e04a5220e9e58a1dc99971ab7cac643",
+ "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643",
"shasum": ""
},
"require": {
@@ -4172,7 +4280,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v6.4.13"
+ "source": "https://github.com/symfony/translation/tree/v6.4.23"
},
"funding": [
{
@@ -4188,20 +4296,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-27T18:14:25+00:00"
+ "time": "2025-06-26T21:24:02+00:00"
},
{
"name": "symfony/translation-contracts",
- "version": "v3.5.0",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
- "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a"
+ "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a",
- "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
+ "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
"shasum": ""
},
"require": {
@@ -4209,12 +4317,12 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.5-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -4250,7 +4358,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0"
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -4266,20 +4374,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:32:20+00:00"
+ "time": "2024-09-27T08:32:26+00:00"
},
{
"name": "symfony/var-exporter",
- "version": "v6.4.13",
+ "version": "v6.4.22",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "0f605f72a363f8743001038a176eeb2a11223b51"
+ "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51",
- "reference": "0f605f72a363f8743001038a176eeb2a11223b51",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f28cf841f5654955c9f88ceaf4b9dc29571988a9",
+ "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9",
"shasum": ""
},
"require": {
@@ -4327,7 +4435,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v6.4.13"
+ "source": "https://github.com/symfony/var-exporter/tree/v6.4.22"
},
"funding": [
{
@@ -4343,20 +4451,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:18:03+00:00"
+ "time": "2025-05-14T13:00:13+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.13",
+ "version": "v6.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9"
+ "reference": "93e29e0deb5f1b2e360adfb389a20d25eb81a27b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
- "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/93e29e0deb5f1b2e360adfb389a20d25eb81a27b",
+ "reference": "93e29e0deb5f1b2e360adfb389a20d25eb81a27b",
"shasum": ""
},
"require": {
@@ -4399,7 +4507,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.13"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.23"
},
"funding": [
{
@@ -4415,7 +4523,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:18:03+00:00"
+ "time": "2025-06-03T06:46:12+00:00"
},
{
"name": "theseer/tokenizer",
@@ -4470,11 +4578,11 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
- "platform-dev": [],
+ "platform": {},
+ "platform-dev": {},
"platform-overrides": {
"php": "8.1"
},
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 21e0cdb3309..0a3fe4fd823 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -16,7 +16,7 @@ default:
- "%paths.base%/../features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -39,7 +39,7 @@ default:
- "%paths.base%/../comments_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -62,7 +62,7 @@ default:
- "%paths.base%/../dav_features"
contexts:
- DavFeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -80,12 +80,14 @@ default:
- CommandLineContext:
baseUrl: http://localhost:8080
ocPath: ../../
+ - PrincipalPropertySearchContext:
+ baseUrl: http://localhost:8080
federation:
paths:
- "%paths.base%/../federation_features"
contexts:
- FederationContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -95,7 +97,7 @@ default:
- "%paths.base%/../files_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -113,12 +115,28 @@ default:
- CommandLineContext:
baseUrl: http://localhost:8080
ocPath: ../../
+ - MetadataContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
+ files_conversion:
+ paths:
+ - "%paths.base%/../file_conversions"
+ contexts:
+ - ConversionsContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
capabilities:
paths:
- "%paths.base%/../capabilities_features"
contexts:
- CapabilitiesContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -128,7 +146,7 @@ default:
- "%paths.base%/../collaboration_features"
contexts:
- CollaborationContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -138,7 +156,7 @@ default:
- "%paths.base%/../sharees_features"
contexts:
- ShareesContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -148,7 +166,7 @@ default:
- "%paths.base%/../sharing_features"
contexts:
- SharingContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -159,7 +177,7 @@ default:
- "%paths.base%/../videoverification_features"
contexts:
- SharingContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -170,7 +188,7 @@ default:
- "%paths.base%/../setup_features"
contexts:
- SetupContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -220,10 +238,41 @@ default:
- "%paths.base%/../remoteapi_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
regular_user_password: 123456
- RemoteContext:
- remote: http://localhost:8080
+ remote: http://localhost:8080
+
+ ratelimiting:
+ paths:
+ - "%paths.base%/../ratelimiting_features"
+ contexts:
+ - RateLimitingContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
+ routing:
+ paths:
+ - "%paths.base%/../routing_features"
+ contexts:
+ - RoutingContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
+ theming:
+ paths:
+ - "%paths.base%/../theming_features"
+ contexts:
+ - FeatureContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
diff --git a/build/integration/data/clouds.jpg b/build/integration/data/clouds.jpg
new file mode 100644
index 00000000000..2433b140766
--- /dev/null
+++ b/build/integration/data/clouds.jpg
Binary files differ
diff --git a/build/integration/data/clouds.jpg.license b/build/integration/data/clouds.jpg.license
new file mode 100644
index 00000000000..d7c54c39d02
--- /dev/null
+++ b/build/integration/data/clouds.jpg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2019 CHUTTERSNAP <https://unsplash.com/@chuttersnap> <https://unsplash.com/photos/blue-clouds-under-white-sky-9AqIdzEc9pY>"
+SPDX-License-Identifier: LicenseRef-Unsplash
diff --git a/build/integration/dav_features/caldav.feature b/build/integration/dav_features/caldav.feature
index 031685b580d..f324f720bbd 100644
--- a/build/integration/dav_features/caldav.feature
+++ b/build/integration/dav_features/caldav.feature
@@ -87,3 +87,8 @@ Feature: caldav
When "user0" requests principal "users/user0" on the endpoint "/remote.php/dav/principals/"
Then The CalDAV response should be multi status
And The CalDAV response should contain a property "{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL" with a href value "/remote.php/dav/calendars/user0/MyCalendar2/"
+
+ Scenario: Should create default calendar on first login
+ Given user "first-login" exists
+ When "first-login" requests calendar "first-login/personal" on the endpoint "/remote.php/dav/calendars/"
+ Then The CalDAV HTTP status code should be "207"
diff --git a/build/integration/dav_features/carddav.feature b/build/integration/dav_features/carddav.feature
index ffee11a284f..35e85639817 100644
--- a/build/integration/dav_features/carddav.feature
+++ b/build/integration/dav_features/carddav.feature
@@ -49,7 +49,6 @@ Feature: carddav
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
Scenario: Exporting the picture of ones own contact
Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
@@ -63,7 +62,6 @@ Feature: carddav
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
Scenario: Create addressbook request for non-existing addressbook of another user
Given user "user0" exists
@@ -79,3 +77,7 @@ Feature: carddav
Then The CardDAV HTTP status code should be "404"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "File not found: admin in 'addressbooks'"
+
+ Scenario: Should create default addressbook on first login
+ Given user "first-login" exists
+ Then "first-login" requests addressbook "first-login/contacts" with statuscode "207" on the endpoint "/remote.php/dav/addressbooks/users/"
diff --git a/build/integration/dav_features/dav-v2.feature b/build/integration/dav_features/dav-v2.feature
index 02d90242a05..dbd2295497f 100644
--- a/build/integration/dav_features/dav-v2.feature
+++ b/build/integration/dav_features/dav-v2.feature
@@ -12,6 +12,16 @@ Feature: dav-v2
When User "user0" moves file "/textfile0.txt" to "/FOLDER/textfile0.txt"
Then the HTTP status code should be "201"
+ Scenario: Moving and overwriting it's parent
+ Given using new dav path
+ And As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/test"
+ And user "user0" created a folder "/test/test"
+ When User "user0" moves file "/test/test" to "/test"
+ Then the HTTP status code should be "403"
+
Scenario: download a file with range using new endpoint
Given using new dav path
And As an "admin"
@@ -31,7 +41,6 @@ Feature: dav-v2
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
And Downloaded content should start with "Welcome to your Nextcloud account!"
Scenario: Doing a GET with a web login should work without CSRF token on the new backend
@@ -93,32 +102,61 @@ Feature: dav-v2
| shareType | 0 |
| permissions | 31 |
| shareWith | user0 |
- And user "user0" accepts last share
+ And user "user0" accepts last share
And As an "user0"
When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
Then the HTTP status code should be "201"
+ Scenario: Uploading a file with very long filename
+ Given using new dav path
+ And As an "admin"
+ And user "user0" exists
+ And user "user0" has a quota of "10 MB"
+ And As an "user0"
+ When User "user0" uploads file "data/textfile.txt" to "/long-filename-with-250-characters-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.txt"
+ Then the HTTP status code should be "201"
+
+ Scenario: Uploading a file with a too long filename
+ Given using new dav path
+ And As an "admin"
+ And user "user0" exists
+ And user "user0" has a quota of "10 MB"
+ And As an "user0"
+ When User "user0" uploads file "data/textfile.txt" to "/long-filename-with-251-characters-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.txt"
+ Then the HTTP status code should be "400"
+
Scenario: Create a search query on image
Given using new dav path
And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
- Then Image search should work
- And the response should be empty
- When User "user0" uploads file "data/green-square-256.png" to "/image.png"
+ And user "user0" exists
+ And As an "user0"
+ When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
+ Then Image search should work
+ And the response should be empty
+ When User "user0" uploads file "data/green-square-256.png" to "/image.png"
Then Image search should work
- And the single response should contain a property "{DAV:}getcontenttype" with value "image/png"
-
- Scenario: Create a search query on favorite
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" uploads file "data/green-square-256.png" to "/fav_image.png"
- Then Favorite search should work
- And the response should be empty
- When user "user0" favorites element "/fav_image.png"
- Then Favorite search should work
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
+ And the single response should contain a property "{DAV:}getcontenttype" with value "image/png"
+ Scenario: Create a search query on favorite
+ Given using new dav path
+ And As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ When User "user0" uploads file "data/green-square-256.png" to "/fav_image.png"
+ Then Favorite search should work
+ And the response should be empty
+ When user "user0" favorites element "/fav_image.png"
+ Then Favorite search should work
+ And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
+
+ Scenario: Create a search query on favorite
+ Given using new dav path
+ And As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ When User "user0" uploads file "data/green-square-256.png" to "/fav_image.png"
+ Then Favorite search should work
+ And the response should be empty
+ When user "user0" favorites element "/fav_image.png"
+ Then Favorite search should work
+ And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
diff --git a/build/integration/dav_features/principal-property-search.feature b/build/integration/dav_features/principal-property-search.feature
new file mode 100644
index 00000000000..b2195489263
--- /dev/null
+++ b/build/integration/dav_features/principal-property-search.feature
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+Feature: principal-property-search
+ Background:
+ Given user "user0" exists
+ Given As an "admin"
+ Given invoking occ with "app:enable --force testing"
+
+ Scenario: Find a principal by a given displayname
+ When searching for a principal matching "user0"
+ Then The search HTTP status code should be "207"
+ And The search response should contain "<d:href>/remote.php/dav/principals/users/user0/</d:href>"
diff --git a/build/integration/dav_features/webdav-related.feature b/build/integration/dav_features/webdav-related.feature
index fdf633bd580..12fd3d44c4f 100644
--- a/build/integration/dav_features/webdav-related.feature
+++ b/build/integration/dav_features/webdav-related.feature
@@ -38,6 +38,43 @@ Feature: webdav-related
Then the HTTP status code should be "204"
And Downloaded content when downloading file "/textfile0.txt" with range "bytes=0-6" should be "Welcome"
+ Scenario: Moving and overwriting it's parent
+ Given using old dav path
+ And As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/test"
+ And user "user0" created a folder "/test/test"
+ When User "user0" moves file "/test/test" to "/test"
+ Then the HTTP status code should be "403"
+
+ Scenario: Moving a file from shared folder to root folder
+ Given using old dav path
+ And user "user0" exists
+ And user "user1" exists
+ And user "user0" created a folder "/testshare"
+ And User "user0" copies file "/welcome.txt" to "/testshare/welcome.txt"
+ And as "user0" creating a share with
+ | path | testshare |
+ | shareType | 0 |
+ | shareWith | user1 |
+ When User "user1" moves file "/testshare/welcome.txt" to "/movedwelcome.txt"
+ Then As an "user1"
+ And Downloaded content when downloading file "/movedwelcome.txt" with range "bytes=0-6" should be "Welcome"
+
+ Scenario: Moving a file from root folder to shared folder
+ Given using old dav path
+ And user "user0" exists
+ And user "user1" exists
+ And user "user0" created a folder "/testshare"
+ And as "user0" creating a share with
+ | path | testshare |
+ | shareType | 0 |
+ | shareWith | user1 |
+ When User "user1" moves file "/welcome.txt" to "/testshare/movedwelcome.txt"
+ Then As an "user1"
+ And Downloaded content when downloading file "/testshare/movedwelcome.txt" with range "bytes=0-6" should be "Welcome"
+
Scenario: Moving a file to a folder with no permissions
Given using old dav path
And As an "admin"
@@ -254,7 +291,6 @@ Feature: webdav-related
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
And Downloaded content should start with "Welcome to your Nextcloud account!"
Scenario: Doing a GET with a web login should work without CSRF token on the old backend
@@ -669,7 +705,7 @@ Feature: webdav-related
And user "user0" uploads new chunk v2 file "2" to id "chunking-random"
And user "user0" uploads new chunk v2 file "4" to id "chunking-random"
And user "user0" moves new chunk v2 file with id "chunking-random"
- Then the upload should fail on object storage
+ Then the upload should fail on object storage
@s3-multipart
Scenario: Upload chunked file with special characters with new chunking v2
diff --git a/build/integration/features/bootstrap/Activity.php b/build/integration/features/bootstrap/Activity.php
new file mode 100644
index 00000000000..4172776304d
--- /dev/null
+++ b/build/integration/features/bootstrap/Activity.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
+
+trait Activity {
+ use BasicStructure;
+
+ /**
+ * @Then last activity should be
+ * @param TableNode $activity
+ */
+ public function lastActivityIs(TableNode $activity): void {
+ $this->sendRequestForJSON('GET', '/apps/activity/api/v2/activity');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $data = json_decode($this->response->getBody()->getContents(), true);
+ $activities = $data['ocs']['data'];
+ /* Sort by id */
+ uasort($activities, fn ($a, $b) => $a['activity_id'] <=> $b['activity_id']);
+ $lastActivity = array_pop($activities);
+ foreach ($activity->getRowsHash() as $key => $value) {
+ Assert::assertEquals($value, $lastActivity[$key]);
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/AppConfiguration.php b/build/integration/features/bootstrap/AppConfiguration.php
index 5f39c58ffeb..e8580ed537b 100644
--- a/build/integration/features/bootstrap/AppConfiguration.php
+++ b/build/integration/features/bootstrap/AppConfiguration.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/Avatar.php b/build/integration/features/bootstrap/Avatar.php
index ffc391c0d45..beebf1c024a 100644
--- a/build/integration/features/bootstrap/Avatar.php
+++ b/build/integration/features/bootstrap/Avatar.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -240,10 +241,10 @@ trait Avatar {
}
private function isSameColor(array $firstColor, array $secondColor, int $allowedDelta = 1) {
- if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
+ if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
return true;
}
@@ -251,8 +252,8 @@ trait Avatar {
}
private function isSameColorComponent(int $firstColorComponent, int $secondColorComponent, int $allowedDelta) {
- if ($firstColorComponent >= ($secondColorComponent - $allowedDelta) &&
- $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
+ if ($firstColorComponent >= ($secondColorComponent - $allowedDelta)
+ && $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
return true;
}
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index b51c0a6a34c..59a4312913e 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -8,6 +9,7 @@ use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Exception\ServerException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
@@ -18,6 +20,7 @@ trait BasicStructure {
use Avatar;
use Download;
use Mail;
+ use Theming;
/** @var string */
private $currentUser = '';
@@ -120,7 +123,11 @@ trait BasicStructure {
* @return string
*/
public function getOCSResponse($response) {
- return simplexml_load_string($response->getBody())->meta[0]->statuscode;
+ $body = simplexml_load_string((string)$response->getBody());
+ if ($body === false) {
+ throw new \RuntimeException('Could not parse OCS response, body is not valid XML');
+ }
+ return $body->meta[0]->statuscode;
}
/**
@@ -170,6 +177,8 @@ trait BasicStructure {
$this->response = $client->request($verb, $fullUrl, $options);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
+ } catch (ServerException $ex) {
+ $this->response = $ex->getResponse();
}
}
@@ -185,8 +194,8 @@ trait BasicStructure {
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = ['admin', 'admin'];
- } elseif (strpos($this->currentUser, 'guest') !== 0) {
- $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
+ } elseif (strpos($this->currentUser, 'anonymous') !== 0) {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
}
if ($body instanceof TableNode) {
$fd = $body->getRowsHash();
diff --git a/build/integration/features/bootstrap/CalDavContext.php b/build/integration/features/bootstrap/CalDavContext.php
index 80f8c53fc4e..459c35089fa 100644
--- a/build/integration/features/bootstrap/CalDavContext.php
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CapabilitiesContext.php b/build/integration/features/bootstrap/CapabilitiesContext.php
index ae32056e17f..7d09ab6ddcf 100644
--- a/build/integration/features/bootstrap/CapabilitiesContext.php
+++ b/build/integration/features/bootstrap/CapabilitiesContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CardDavContext.php b/build/integration/features/bootstrap/CardDavContext.php
index a59f0d56f96..733c98dca02 100644
--- a/build/integration/features/bootstrap/CardDavContext.php
+++ b/build/integration/features/bootstrap/CardDavContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/ChecksumsContext.php b/build/integration/features/bootstrap/ChecksumsContext.php
index 3392f8545d9..c8abf91127e 100644
--- a/build/integration/features/bootstrap/ChecksumsContext.php
+++ b/build/integration/features/bootstrap/ChecksumsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php
index 84b3dfd447f..924d723daa6 100644
--- a/build/integration/features/bootstrap/CommandLine.php
+++ b/build/integration/features/bootstrap/CommandLine.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php
index 5ea8d12a970..e7764356270 100644
--- a/build/integration/features/bootstrap/CommandLineContext.php
+++ b/build/integration/features/bootstrap/CommandLineContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -109,19 +110,6 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)" with received shares$/
- */
- public function transferringOwnershipPathWithIncomingShares($path, $user1, $user2) {
- $path = '--path=' . $path;
- if ($this->runOcc(['files:transfer-ownership', $path, $user1, $user2, '--transfer-incoming-shares=1']) === 0) {
- $this->lastTransferPath = $this->findLastTransferFolderForUser($user1, $user2);
- } else {
- // failure
- $this->lastTransferPath = null;
- }
- }
-
- /**
* @When /^using received transfer folder of "([^"]+)" as dav path$/
*/
public function usingTransferFolderAsDavPath($user) {
diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php
index 17795a48fb4..53001b1c204 100644
--- a/build/integration/features/bootstrap/CommentsContext.php
+++ b/build/integration/features/bootstrap/CommentsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/ContactsMenu.php b/build/integration/features/bootstrap/ContactsMenu.php
index 4fc3c03c5e9..f6bf6b9422b 100644
--- a/build/integration/features/bootstrap/ContactsMenu.php
+++ b/build/integration/features/bootstrap/ContactsMenu.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ConversionsContext.php b/build/integration/features/bootstrap/ConversionsContext.php
new file mode 100644
index 00000000000..ccd14c460f8
--- /dev/null
+++ b/build/integration/features/bootstrap/ConversionsContext.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+use Behat\Gherkin\Node\TableNode;
+
+class ConversionsContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
+ use BasicStructure;
+ use WebDav;
+
+ /** @BeforeScenario */
+ public function setUpScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(true);
+ }
+
+ /** @AfterScenario */
+ public function tearDownScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(false);
+ }
+
+ protected function resetAppConfigs() {
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileId(string $user, string $path, string $mime) {
+ $this->userConvertsTheSavedFileIdTo($user, $path, $mime, null);
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)" and saves it to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileIdTo(string $user, string $path, string $mime, ?string $destination) {
+ try {
+ $fileId = $this->getFileIdForPath($user, $path);
+ } catch (Exception $e) {
+ // return a fake value to keep going and be able to test the error
+ $fileId = 0;
+ }
+
+ $data = [['fileId', $fileId], ['targetMimeType', $mime]];
+ if ($destination !== null) {
+ $data[] = ['destination', $destination];
+ }
+
+ $this->asAn($user);
+ $this->sendingToWith('post', '/apps/files/api/v1/convert', new TableNode($data));
+ }
+}
diff --git a/build/integration/features/bootstrap/DavFeatureContext.php b/build/integration/features/bootstrap/DavFeatureContext.php
index acca52ccafc..ec6085cff98 100644
--- a/build/integration/features/bootstrap/DavFeatureContext.php
+++ b/build/integration/features/bootstrap/DavFeatureContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Download.php b/build/integration/features/bootstrap/Download.php
index 2a66f7c3d89..549a033346e 100644
--- a/build/integration/features/bootstrap/Download.php
+++ b/build/integration/features/bootstrap/Download.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ExternalStorage.php b/build/integration/features/bootstrap/ExternalStorage.php
index b1e4c92810b..8fe2653a026 100644
--- a/build/integration/features/bootstrap/ExternalStorage.php
+++ b/build/integration/features/bootstrap/ExternalStorage.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/FakeSMTPHelper.php b/build/integration/features/bootstrap/FakeSMTPHelper.php
index caf2139faab..32387869edd 100644
--- a/build/integration/features/bootstrap/FakeSMTPHelper.php
+++ b/build/integration/features/bootstrap/FakeSMTPHelper.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php
index 59f1d0068dd..ab37556f931 100644
--- a/build/integration/features/bootstrap/FeatureContext.php
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -13,9 +14,16 @@ require __DIR__ . '/../../vendor/autoload.php';
* Features context.
*/
class FeatureContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
use ContactsMenu;
use ExternalStorage;
use Search;
use WebDav;
use Trashbin;
+
+ protected function resetAppConfigs(): void {
+ $this->deleteServerConfig('bruteForce', 'whitelist_0');
+ $this->deleteServerConfig('bruteForce', 'whitelist_1');
+ $this->deleteServerConfig('bruteforcesettings', 'apply_allowlist_to_ratelimit');
+ }
}
diff --git a/build/integration/features/bootstrap/FederationContext.php b/build/integration/features/bootstrap/FederationContext.php
index bbd81396df5..95dc8119ad6 100644
--- a/build/integration/features/bootstrap/FederationContext.php
+++ b/build/integration/features/bootstrap/FederationContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -7,6 +8,7 @@
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
require __DIR__ . '/../../vendor/autoload.php';
@@ -168,8 +170,52 @@ class FederationContext implements Context, SnippetAcceptingContext {
self::$phpFederatedServerPid = '';
}
+ /**
+ * @BeforeScenario @TrustedFederation
+ */
+ public function theServersAreTrustingEachOther() {
+ $this->asAn('admin');
+ // Trust the remote server on the local server
+ $this->usingServer('LOCAL');
+ $this->sendRequestForJSON('POST', '/apps/federation/trusted-servers', ['url' => 'http://localhost:' . getenv('PORT')]);
+ Assert::assertTrue(($this->response->getStatusCode() === 200 || $this->response->getStatusCode() === 409));
+
+ // Trust the local server on the remote server
+ $this->usingServer('REMOTE');
+ $this->sendRequestForJSON('POST', '/apps/federation/trusted-servers', ['url' => 'http://localhost:' . getenv('PORT_FED')]);
+ // If the server is already trusted, we expect a 409
+ Assert::assertTrue(($this->response->getStatusCode() === 200 || $this->response->getStatusCode() === 409));
+ }
+
+ /**
+ * @AfterScenario @TrustedFederation
+ */
+ public function theServersAreNoLongerTrustingEachOther() {
+ $this->asAn('admin');
+ // Untrust the remote servers on the local server
+ $this->usingServer('LOCAL');
+ $this->sendRequestForJSON('GET', '/apps/federation/trusted-servers');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $trustedServersIDs = array_map(fn ($server) => $server->id, json_decode($this->response->getBody())->ocs->data);
+ foreach ($trustedServersIDs as $id) {
+ $this->sendRequestForJSON('DELETE', '/apps/federation/trusted-servers/' . $id);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+
+ // Untrust the local server on the remote server
+ $this->usingServer('REMOTE');
+ $this->sendRequestForJSON('GET', '/apps/federation/trusted-servers');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $trustedServersIDs = array_map(fn ($server) => $server->id, json_decode($this->response->getBody())->ocs->data);
+ foreach ($trustedServersIDs as $id) {
+ $this->sendRequestForJSON('DELETE', '/apps/federation/trusted-servers/' . $id);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+ }
+
protected function resetAppConfigs() {
$this->deleteServerConfig('files_sharing', 'incoming_server2server_group_share_enabled');
$this->deleteServerConfig('files_sharing', 'outgoing_server2server_group_share_enabled');
+ $this->deleteServerConfig('files_sharing', 'federated_trusted_share_auto_accept');
}
}
diff --git a/build/integration/features/bootstrap/FilesDropContext.php b/build/integration/features/bootstrap/FilesDropContext.php
index 1b9d598645f..0c437f28a72 100644
--- a/build/integration/features/bootstrap/FilesDropContext.php
+++ b/build/integration/features/bootstrap/FilesDropContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -15,7 +16,7 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
/**
* @When Dropping file :path with :content
*/
- public function droppingFileWith($path, $content, $nickName = null) {
+ public function droppingFileWith($path, $content, $nickname = null) {
$client = new Client();
$options = [];
if (count($this->lastShareData->data->element) > 0) {
@@ -28,11 +29,11 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
$fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$path");
$options['headers'] = [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest'
+ 'X-REQUESTED-WITH' => 'XMLHttpRequest',
];
- if ($nickName) {
- $options['headers']['X-NC-NICKNAME'] = $nickName;
+ if ($nickname) {
+ $options['headers']['X-NC-NICKNAME'] = $nickname;
}
$options['body'] = \GuzzleHttp\Psr7\Utils::streamFor($content);
@@ -43,20 +44,20 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
$this->response = $e->getResponse();
}
}
-
-
+
+
/**
* @When Dropping file :path with :content as :nickName
*/
- public function droppingFileWithAs($path, $content, $nickName) {
- $this->droppingFileWith($path, $content, $nickName);
+ public function droppingFileWithAs($path, $content, $nickname) {
+ $this->droppingFileWith($path, $content, $nickname);
}
/**
* @When Creating folder :folder in drop
*/
- public function creatingFolderInDrop($folder) {
+ public function creatingFolderInDrop($folder, $nickname = null) {
$client = new Client();
$options = [];
if (count($this->lastShareData->data->element) > 0) {
@@ -69,13 +70,25 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
$fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$folder");
$options['headers'] = [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest'
+ 'X-REQUESTED-WITH' => 'XMLHttpRequest',
];
+ if ($nickname) {
+ $options['headers']['X-NC-NICKNAME'] = $nickname;
+ }
+
try {
$this->response = $client->request('MKCOL', $fullUrl, $options);
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->response = $e->getResponse();
}
}
+
+
+ /**
+ * @When Creating folder :folder in drop as :nickName
+ */
+ public function creatingFolderInDropWithNickname($folder, $nickname) {
+ return $this->creatingFolderInDrop($folder, $nickname);
+ }
}
diff --git a/build/integration/features/bootstrap/LDAPContext.php b/build/integration/features/bootstrap/LDAPContext.php
index f0181b36c71..986dced77a1 100644
--- a/build/integration/features/bootstrap/LDAPContext.php
+++ b/build/integration/features/bootstrap/LDAPContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Mail.php b/build/integration/features/bootstrap/Mail.php
index d28c24730ba..d48ed6399c5 100644
--- a/build/integration/features/bootstrap/Mail.php
+++ b/build/integration/features/bootstrap/Mail.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/MetadataContext.php b/build/integration/features/bootstrap/MetadataContext.php
new file mode 100644
index 00000000000..32042590c86
--- /dev/null
+++ b/build/integration/features/bootstrap/MetadataContext.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Then;
+use Behat\Step\When;
+use PHPUnit\Framework\Assert;
+use Sabre\DAV\Client as SClient;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class MetadataContext implements Context {
+ private string $davPath = '/remote.php/dav';
+
+ public function __construct(
+ private string $baseUrl,
+ private array $admin,
+ private string $regular_user_password,
+ ) {
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ #[When('User :user sets the :metadataKey prop with value :metadataValue on :fileName')]
+ public function userSetsProp(string $user, string $metadataKey, string $metadataValue, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:set>
+ <d:prop>
+ <nc:' . $metadataKey . '>' . $metadataValue . '</nc:' . $metadataKey . '>
+ </d:prop>
+ </d:set>
+</d:propertyupdate>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
+ }
+
+ #[When('User :user deletes the :metadataKey prop on :fileName')]
+ public function userDeletesProp(string $user, string $metadataKey, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:remove>
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+ </d:remove>
+</d:propertyupdate>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
+ }
+
+ #[Then('User :user should see the prop :metadataKey equal to :metadataValue for file :fileName')]
+ public function checkPropForFile(string $user, string $metadataKey, string $metadataValue, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+</d:propfind>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
+ $parsedResponse = $client->parseMultistatus($response['body']);
+
+ Assert::assertEquals($parsedResponse[$davUrl]['200']['{http://nextcloud.com/ns}' . $metadataKey], $metadataValue);
+ }
+
+ #[Then('User :user should not see the prop :metadataKey for file :fileName')]
+ public function checkPropDoesNotExistsForFile(string $user, string $metadataKey, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+</d:propfind>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
+ $parsedResponse = $client->parseMultistatus($response['body']);
+
+ Assert::assertEquals($parsedResponse[$davUrl]['404']['{http://nextcloud.com/ns}' . $metadataKey], null);
+ }
+
+ private function getDavUrl(string $user, string $fileName) {
+ return $this->davPath . '/files/' . $user . $fileName;
+ }
+}
diff --git a/build/integration/features/bootstrap/PrincipalPropertySearchContext.php b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
new file mode 100644
index 00000000000..9dfd9379240
--- /dev/null
+++ b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use GuzzleHttp\BodySummarizer;
+use GuzzleHttp\Client;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\ResponseInterface;
+
+class PrincipalPropertySearchContext implements Context {
+ private string $baseUrl;
+ private Client $client;
+ private ResponseInterface $response;
+
+ public function __construct(string $baseUrl) {
+ $this->baseUrl = $baseUrl;
+
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ /** @BeforeScenario */
+ public function setUpScenario(): void {
+ $this->client = $this->createGuzzleInstance();
+ }
+
+ /**
+ * Create a Guzzle client with a higher truncateAt value to read full error responses.
+ */
+ private function createGuzzleInstance(): Client {
+ $bodySummarizer = new BodySummarizer(2048);
+
+ $stack = new HandlerStack(Utils::chooseHandler());
+ $stack->push(Middleware::httpErrors($bodySummarizer), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return new Client(['handler' => $stack]);
+ }
+
+ /**
+ * @When searching for a principal matching :match
+ * @param string $match
+ * @throws \Exception
+ */
+ public function principalPropertySearch(string $match) {
+ $davUrl = $this->baseUrl . '/remote.php/dav/';
+ $user = 'admin';
+ $password = 'admin';
+
+ $this->response = $this->client->request(
+ 'REPORT',
+ $davUrl,
+ [
+ 'body' => '<x0:principal-property-search xmlns:x0="DAV:" test="anyof">
+ <x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ </x0:prop>
+ <x0:match>' . $match . '</x0:match>
+ </x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x1:calendar-user-type xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:calendar-user-address-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x0:principal-URL/>
+ <x0:alternate-URI-set/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ <x3:language xmlns:x3="http://nextcloud.com/ns"/>
+ <x1:calendar-home-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-inbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-outbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-default-calendar-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x3:resource-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-make xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-model xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-is-electric xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-range xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person-vcard xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-address xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-story xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-room-number xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-features xmlns:x3="http://nextcloud.com/ns"/>
+ </x0:prop>
+ <x0:apply-to-principal-collection-set/>
+</x0:principal-property-search>
+',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/xml; charset=UTF-8',
+ 'Depth' => '0',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * @Then The search HTTP status code should be :code
+ * @param string $code
+ * @throws \Exception
+ */
+ public function theHttpStatusCodeShouldBe(string $code): void {
+ if ((int)$code !== $this->response->getStatusCode()) {
+ throw new \Exception('Expected ' . (int)$code . ' got ' . $this->response->getStatusCode());
+ }
+ }
+
+ /**
+ * @Then The search response should contain :needle
+ * @param string $needle
+ * @throws \Exception
+ */
+ public function theResponseShouldContain(string $needle): void {
+ $body = $this->response->getBody()->getContents();
+
+ if (str_contains($body, $needle) === false) {
+ throw new \Exception('Response does not contain "' . $needle . '"');
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php
index 9e3e8ccfd8b..935ad2a4a1d 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -1,9 +1,12 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
+use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Message\ResponseInterface;
use PHPUnit\Framework\Assert;
@@ -124,7 +127,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function userHasSetting($user, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
@@ -145,12 +148,43 @@ trait Provisioning {
if (isset($value['element']) && in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
$expectedValues = explode(';', $setting[1]);
foreach ($expectedValues as $expected) {
- Assert::assertTrue(in_array($expected, $value['element'], true));
+ Assert::assertTrue(in_array($expected, $value['element'], true), 'Data wrong for field: ' . $setting[0]);
}
} elseif (isset($value[0])) {
- Assert::assertEqualsCanonicalizing($setting[1], $value[0]);
+ Assert::assertEqualsCanonicalizing($setting[1], $value[0], 'Data wrong for field: ' . $setting[0]);
} else {
- Assert::assertEquals('', $setting[1]);
+ Assert::assertEquals('', $setting[1], 'Data wrong for field: ' . $setting[0]);
+ }
+ }
+ }
+
+ /**
+ * @Then /^user "([^"]*)" has the following profile data$/
+ */
+ public function userHasProfileData(string $user, ?TableNode $settings): void {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/profile/$user";
+ $client = new Client();
+ $options = [];
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
+ $options['headers'] = [
+ 'OCS-APIREQUEST' => 'true',
+ 'Accept' => 'application/json',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ $body = $response->getBody()->getContents();
+ $data = json_decode($body, true);
+ $data = $data['ocs']['data'];
+ foreach ($settings->getRows() as $setting) {
+ Assert::assertArrayHasKey($setting[0], $data, 'Profile data field missing: ' . $setting[0]);
+ if ($setting[1] === 'NULL') {
+ Assert::assertNull($data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
+ } else {
+ Assert::assertEquals($setting[1], $data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
}
}
}
@@ -159,7 +193,7 @@ trait Provisioning {
* @Then /^group "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function groupHasSetting($group, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/groups/details?search=$group";
@@ -191,7 +225,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has editable fields$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $fields
+ * @param TableNode|null $fields
*/
public function userHasEditableFields($user, $fields) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/user/fields";
@@ -221,9 +255,9 @@ trait Provisioning {
* @Then /^search users by phone for region "([^"]*)" with$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
- public function searchUserByPhone($region, \Behat\Gherkin\Node\TableNode $searchTable) {
+ public function searchUserByPhone($region, TableNode $searchTable) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/search/by-phone";
$client = new Client();
$options = [];
@@ -624,10 +658,10 @@ trait Provisioning {
/**
* @Then /^users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfUsersResponded($this->response);
@@ -637,10 +671,10 @@ trait Provisioning {
/**
* @Then /^phone matches returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function thePhoneUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRowsHash();
$listCheckedElements = simplexml_load_string($this->response->getBody())->data;
$respondedArray = json_decode(json_encode($listCheckedElements), true);
@@ -650,10 +684,10 @@ trait Provisioning {
/**
* @Then /^detailed users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theDetailedUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfDetailedUsersResponded($this->response);
@@ -664,10 +698,10 @@ trait Provisioning {
/**
* @Then /^groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfGroupsResponded($this->response);
@@ -677,10 +711,10 @@ trait Provisioning {
/**
* @Then /^subadmin groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfSubadminsResponded($this->response);
@@ -690,10 +724,10 @@ trait Provisioning {
/**
* @Then /^apps returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $appList
+ * @param TableNode|null $appList
*/
public function theAppsShouldBe($appList) {
- if ($appList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($appList instanceof TableNode) {
$apps = $appList->getRows();
$appsSimplified = $this->simplifyArray($apps);
$respondedArray = $this->getArrayOfAppsResponded($this->response);
@@ -703,7 +737,7 @@ trait Provisioning {
/**
* @Then /^subadmin users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminUsersShouldBe($groupsList) {
$this->theSubadminGroupsShouldBe($groupsList);
@@ -882,7 +916,7 @@ trait Provisioning {
* @param string $quota
*/
public function userHasAQuotaOf($user, $quota) {
- $body = new \Behat\Gherkin\Node\TableNode([
+ $body = new TableNode([
0 => ['key', 'quota'],
1 => ['value', $quota],
]);
@@ -950,7 +984,7 @@ trait Provisioning {
/**
* @Then /^user "([^"]*)" has not$/
*/
- public function userHasNotSetting($user, \Behat\Gherkin\Node\TableNode $settings) {
+ public function userHasNotSetting($user, TableNode $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
$client = new Client();
$options = [];
diff --git a/build/integration/features/bootstrap/RateLimitingContext.php b/build/integration/features/bootstrap/RateLimitingContext.php
new file mode 100644
index 00000000000..15c8c5c8379
--- /dev/null
+++ b/build/integration/features/bootstrap/RateLimitingContext.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Behat\Context\Context;
+
+class RateLimitingContext implements Context {
+ use BasicStructure;
+ use CommandLine;
+ use Provisioning;
+
+ /**
+ * @BeforeScenario @RateLimiting
+ */
+ public function enableRateLimiting() {
+ // Enable rate limiting for the tests.
+ // Ratelimiting is disabled by default, so we need to enable it
+ $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'true', '--type', 'bool']);
+ }
+
+ /**
+ * @AfterScenario @RateLimiting
+ */
+ public function disableRateLimiting() {
+ // Restore the default rate limiting configuration.
+ // Ratelimiting is disabled by default, so we need to disable it
+ $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'false', '--type', 'bool']);
+ }
+}
diff --git a/build/integration/features/bootstrap/RemoteContext.php b/build/integration/features/bootstrap/RemoteContext.php
index ae9da4b3614..6102f686ea7 100644
--- a/build/integration/features/bootstrap/RemoteContext.php
+++ b/build/integration/features/bootstrap/RemoteContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/RoutingContext.php b/build/integration/features/bootstrap/RoutingContext.php
new file mode 100644
index 00000000000..762570547e0
--- /dev/null
+++ b/build/integration/features/bootstrap/RoutingContext.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class RoutingContext implements Context, SnippetAcceptingContext {
+ use Provisioning;
+ use AppConfiguration;
+ use CommandLine;
+
+ protected function resetAppConfigs(): void {
+ }
+}
diff --git a/build/integration/features/bootstrap/Search.php b/build/integration/features/bootstrap/Search.php
index 47259be769c..49a4fe92822 100644
--- a/build/integration/features/bootstrap/Search.php
+++ b/build/integration/features/bootstrap/Search.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/SetupContext.php b/build/integration/features/bootstrap/SetupContext.php
index 96cb00d8601..aa131cec597 100644
--- a/build/integration/features/bootstrap/SetupContext.php
+++ b/build/integration/features/bootstrap/SetupContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ShareesContext.php b/build/integration/features/bootstrap/ShareesContext.php
index e152a749bfa..37e0e63e547 100644
--- a/build/integration/features/bootstrap/ShareesContext.php
+++ b/build/integration/features/bootstrap/ShareesContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php
index a58a1b4fda3..0cc490ff110 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -54,8 +55,12 @@ trait Sharing {
$fd = $body->getRowsHash();
if (array_key_exists('expireDate', $fd)) {
$dateModification = $fd['expireDate'];
- if (!empty($dateModification)) {
+ if ($dateModification === 'null') {
+ $fd['expireDate'] = null;
+ } elseif (!empty($dateModification)) {
$fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
+ } else {
+ $fd['expireDate'] = '';
}
}
$options['form_params'] = $fd;
@@ -332,6 +337,8 @@ trait Sharing {
return $this->isExpectedUrl((string)$data->$field, 'index.php/s/');
} elseif ($contentExpected == $data->$field) {
return true;
+ } else {
+ print($data->$field);
}
return false;
}
@@ -557,18 +564,18 @@ trait Sharing {
];
$expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
- if (!array_key_exists('uid_file_owner', $expectedFields) &&
- array_key_exists('uid_owner', $expectedFields)) {
+ if (!array_key_exists('uid_file_owner', $expectedFields)
+ && array_key_exists('uid_owner', $expectedFields)) {
$expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
}
- if (!array_key_exists('displayname_file_owner', $expectedFields) &&
- array_key_exists('displayname_owner', $expectedFields)) {
+ if (!array_key_exists('displayname_file_owner', $expectedFields)
+ && array_key_exists('displayname_owner', $expectedFields)) {
$expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
}
- if (array_key_exists('share_type', $expectedFields) &&
- $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */ &&
- array_key_exists('share_with', $expectedFields)) {
+ if (array_key_exists('share_type', $expectedFields)
+ && $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
+ && array_key_exists('share_with', $expectedFields)) {
if ($expectedFields['share_with'] === 'private_conversation') {
$expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
} else {
diff --git a/build/integration/features/bootstrap/SharingContext.php b/build/integration/features/bootstrap/SharingContext.php
index 97c2a35ad84..a9dd99108a9 100644
--- a/build/integration/features/bootstrap/SharingContext.php
+++ b/build/integration/features/bootstrap/SharingContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -17,6 +18,7 @@ class SharingContext implements Context, SnippetAcceptingContext {
use Trashbin;
use AppConfiguration;
use CommandLine;
+ use Activity;
protected function resetAppConfigs() {
$this->deleteServerConfig('core', 'shareapi_default_permissions');
@@ -27,6 +29,9 @@ class SharingContext implements Context, SnippetAcceptingContext {
$this->deleteServerConfig('core', 'shareapi_default_expire_date');
$this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
$this->deleteServerConfig('core', 'link_defaultExpDays');
+ $this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
+ $this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
+ $this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
$this->runOcc(['config:system:delete', 'share_folder']);
}
diff --git a/build/integration/features/bootstrap/TagsContext.php b/build/integration/features/bootstrap/TagsContext.php
index 262271e3710..c64626de68d 100644
--- a/build/integration/features/bootstrap/TagsContext.php
+++ b/build/integration/features/bootstrap/TagsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -255,9 +256,9 @@ class TagsContext implements \Behat\Behat\Context\Context {
foreach ($table->getRowsHash() as $rowDisplayName => $row) {
foreach ($tags as $key => $tag) {
if (
- $tag['display-name'] === $rowDisplayName &&
- $tag['user-visible'] === $row[0] &&
- $tag['user-assignable'] === $row[1]
+ $tag['display-name'] === $rowDisplayName
+ && $tag['user-visible'] === $row[0]
+ && $tag['user-assignable'] === $row[1]
) {
unset($tags[$key]);
}
diff --git a/build/integration/features/bootstrap/TalkContext.php b/build/integration/features/bootstrap/TalkContext.php
index fe248e1af7c..6f351c30ccf 100644
--- a/build/integration/features/bootstrap/TalkContext.php
+++ b/build/integration/features/bootstrap/TalkContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Theming.php b/build/integration/features/bootstrap/Theming.php
new file mode 100644
index 00000000000..f44a6533a1b
--- /dev/null
+++ b/build/integration/features/bootstrap/Theming.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait Theming {
+
+ private bool $undoAllThemingChangesAfterScenario = false;
+
+ /**
+ * @AfterScenario
+ */
+ public function undoAllThemingChanges() {
+ if (!$this->undoAllThemingChangesAfterScenario) {
+ return;
+ }
+
+ $this->loggingInUsingWebAs('admin');
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/undoAllChanges');
+
+ $this->undoAllThemingChangesAfterScenario = false;
+ }
+
+ /**
+ * @When logged in admin uploads theming image for :key from file :source
+ *
+ * @param string $key
+ * @param string $source
+ */
+ public function loggedInAdminUploadsThemingImageForFromFile(string $key, string $source) {
+ $this->undoAllThemingChangesAfterScenario = true;
+
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
+
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/uploadImage?key=' . $key,
+ [
+ 'multipart' => [
+ [
+ 'name' => 'image',
+ 'contents' => $file
+ ]
+ ]
+ ]);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+}
diff --git a/build/integration/features/bootstrap/Trashbin.php b/build/integration/features/bootstrap/Trashbin.php
index 6c8fd5e4fb6..dfcc23289a7 100644
--- a/build/integration/features/bootstrap/Trashbin.php
+++ b/build/integration/features/bootstrap/Trashbin.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2017 ownCloud GmbH
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index e71502b6b0c..2cb37002ac0 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -262,7 +263,11 @@ trait WebDav {
'Accept' => 'application/zip'
];
- $this->response = $client->request('GET', $fullUrl, $options);
+ try {
+ $this->response = $client->request('GET', $fullUrl, $options);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
}
/**
diff --git a/build/integration/features/contacts-menu.feature b/build/integration/features/contacts-menu.feature
index f01b34aa1ba..772c0e5405c 100644
--- a/build/integration/features/contacts-menu.feature
+++ b/build/integration/features/contacts-menu.feature
@@ -71,8 +71,6 @@ Feature: contacts-menu
And searched contact "1" is named "Test name"
And searched contact "2" is named "user2"
-
-
Scenario: users can not be found by display name if visibility is private
Given user "user0" exists
And user "user1" exists
@@ -80,11 +78,11 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
# Disabled because it regularly fails on drone:
@@ -98,11 +96,11 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| email | another_test@example.com |
- | emailScope | contacts |
+ | emailScope | v2-federated |
# Disabled because it regularly fails on drone:
# When Logging in using web as "user0"
# And searching for contacts matching with "test"
@@ -116,15 +114,15 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
| email | another_test@example.com |
- | emailScope | contacts |
+ | emailScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "2" contacts
@@ -140,9 +138,9 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
@@ -154,9 +152,9 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | emailScope | contacts |
+ | emailScope | v2-federated |
# Disabled because it regularly fails on drone:
# When Logging in using web as "user0"
# And searching for contacts matching with "test"
@@ -170,7 +168,7 @@ Feature: contacts-menu
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
@@ -185,7 +183,7 @@ Feature: contacts-menu
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | emailScope | private |
+ | emailScope | v2-private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | email |
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 12498bedd7f..8fcfb076497 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -4,6 +4,9 @@
Feature: provisioning
Background:
Given using api version "1"
+ Given parameter "whitelist_0" of app "bruteForce" is set to "127.0.0.1"
+ Given parameter "whitelist_1" of app "bruteForce" is set to "::1"
+ Given parameter "apply_allowlist_to_ratelimit" of app "bruteforcesettings" is set to "true"
Scenario: Getting an not existing user
Given As an "admin"
@@ -69,7 +72,8 @@ Feature: provisioning
| phone |
| address |
| website |
- | twitter |
+ | twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
@@ -86,6 +90,7 @@ Feature: provisioning
| address |
| website |
| twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
@@ -101,6 +106,7 @@ Feature: provisioning
| address |
| website |
| twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
@@ -155,6 +161,9 @@ Feature: provisioning
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | twitter |
| value | Nextcloud |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | bluesky |
+ | value | nextcloud.bsky.social |
And the OCS status code should be "100"
And the HTTP status code should be "200"
Then user "brand-new-user" has
@@ -165,7 +174,37 @@ Feature: provisioning
| phone | +4971125242890 |
| address | Foo Bar Town |
| website | https://nextcloud.com |
- | twitter | Nextcloud |
+ | twitter | Nextcloud |
+ | bluesky | nextcloud.bsky.social |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | organisation |
+ | value | Nextcloud GmbH |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | role |
+ | value | Engineer |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has the following profile data
+ | userId | brand-new-user |
+ | displayname | Brand New User |
+ | organisation | Nextcloud GmbH |
+ | role | Engineer |
+ | address | Foo Bar Town |
+ | timezone | UTC |
+ | timezoneOffset | 0 |
+ | pronouns | NULL |
+
+ Scenario: Edit a user with mixed case emails
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | email |
+ | value | mixed-CASE@Nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | email | mixed-case@nextcloud.com |
Scenario: Edit a user account properties scopes
Given user "brand-new-user" exists
@@ -180,6 +219,11 @@ Feature: provisioning
| value | v2-local |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | blueskyScope |
+ | value | v2-local |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | addressScope |
| value | v2-federated |
@@ -190,21 +234,6 @@ Feature: provisioning
| value | v2-published |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | websiteScope |
- | value | public |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | displaynameScope |
- | value | contacts |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | avatarScope |
- | value | private |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | email |
| value | no-reply@nextcloud.com |
@@ -230,12 +259,10 @@ Feature: provisioning
Then user "brand-new-user" has
| id | brand-new-user |
| phoneScope | v2-private |
- | twitterScope | v2-local |
+ | twitterScope | v2-local |
+ | blueskyScope | v2-local |
| addressScope | v2-federated |
| emailScope | v2-published |
- | websiteScope | v2-published |
- | displaynameScope | v2-federated |
- | avatarScope | v2-local |
Scenario: Edit a user account multivalue property scopes
Given user "brand-new-user" exists
@@ -450,6 +477,7 @@ Feature: provisioning
Then groups returned are
| España |
| admin |
+ | hidden_group |
| new-group |
Scenario: create a subadmin
@@ -604,6 +632,7 @@ Feature: provisioning
| settings |
| sharebymail |
| systemtags |
+ | testing |
| theming |
| twofactor_backupcodes |
| updatenotification |
@@ -629,6 +658,7 @@ Feature: provisioning
And the HTTP status code should be "200"
Scenario: enable an app
+ Given invoking occ with "app:disable testing"
Given As an "admin"
And app "testing" is disabled
When sending "POST" to "/cloud/apps/testing"
@@ -643,12 +673,14 @@ Feature: provisioning
And the HTTP status code should be "200"
Scenario: disable an app
+ Given invoking occ with "app:enable testing"
Given As an "admin"
And app "testing" is enabled
When sending "DELETE" to "/cloud/apps/testing"
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And app "testing" is disabled
+ Given invoking occ with "app:enable testing"
Scenario: disable an user
Given As an "admin"
diff --git a/build/integration/federation_features/federated.feature b/build/integration/federation_features/federated.feature
index 81a3d3abd02..d3a414cb804 100644
--- a/build/integration/federation_features/federated.feature
+++ b/build/integration/federation_features/federated.feature
@@ -8,7 +8,7 @@ Feature: federated
Scenario: Federate share a file with another server
Given Using server "REMOTE"
And user "user1" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
When User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
Then the OCS status code should be "100"
@@ -30,6 +30,12 @@ Feature: federated
| displayname_owner | user0 |
| share_with | user1@REMOTE |
| share_with_displayname | user1 |
+ Given Using server "REMOTE"
+ And As an "user1"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 0 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ Then the list of returned shares has 1 shares
Scenario: Federated group share a file with another server
Given Using server "REMOTE"
@@ -40,7 +46,7 @@ Feature: federated
And As an "admin"
And Add user "gs-user1" to the group "group1"
And Add user "gs-user2" to the group "group1"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And parameter "outgoing_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
And user "gs-user0" exists
When User "gs-user0" from server "LOCAL" shares "/textfile0.txt" with group "group1" from server "REMOTE"
@@ -64,11 +70,10 @@ Feature: federated
| share_with | group1@REMOTE |
| share_with_displayname | group1@REMOTE |
-
Scenario: Federate share a file with local server
Given Using server "LOCAL"
And user "user0" exists
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And user "user1" exists
When User "user1" from server "REMOTE" shares "/textfile0.txt" with user "user0" from server "LOCAL"
Then the OCS status code should be "100"
@@ -94,10 +99,10 @@ Feature: federated
Scenario: Remote sharee can see the pending share
Given Using server "REMOTE"
And user "user1" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
And User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
Then the OCS status code should be "100"
@@ -122,11 +127,11 @@ Feature: federated
And As an "admin"
And Add user "gs-user1" to the group "group1"
And Add user "gs-user2" to the group "group1"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And parameter "outgoing_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
And user "gs-user0" exists
When User "gs-user0" from server "LOCAL" shares "/textfile0.txt" with group "group1" from server "REMOTE"
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "gs-user1"
When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
Then the OCS status code should be "100"
@@ -159,7 +164,7 @@ Feature: federated
Scenario: accept a pending remote share
Given Using server "REMOTE"
And user "user1" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
And User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
When User "user1" from server "REMOTE" accepts last pending share
@@ -175,7 +180,7 @@ Feature: federated
And As an "admin"
And Add user "gs-user1" to the group "group1"
And Add user "gs-user2" to the group "group1"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And parameter "outgoing_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
And user "gs-user0" exists
When User "gs-user0" from server "LOCAL" shares "/textfile0.txt" with group "group1" from server "REMOTE"
@@ -187,45 +192,45 @@ Feature: federated
Given Using server "REMOTE"
And user "user1" exists
And user "user2" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
And User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
And User "user1" from server "REMOTE" accepts last pending share
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
When creating a share with
| path | /textfile0 (2).txt |
| shareType | 0 |
| shareWith | user2 |
| permissions | 19 |
- #Then the OCS status code should be "100"
- #And the HTTP status code should be "200"
- #And Share fields of last share match with
- # | id | A_NUMBER |
- # | item_type | file |
- # | item_source | A_NUMBER |
- # | share_type | 0 |
- # | file_source | A_NUMBER |
- # | path | /textfile0 (2).txt |
- # | permissions | 19 |
- # | stime | A_NUMBER |
- # | storage | A_NUMBER |
- # | mail_send | 1 |
- # | uid_owner | user1 |
- # | file_parent | A_NUMBER |
- # | displayname_owner | user1 |
- # | share_with | user2 |
- # | share_with_displayname | user2 |
+ # Then the OCS status code should be "100"
+ # And the HTTP status code should be "200"
+ # And Share fields of last share match with
+ # | id | A_NUMBER |
+ # | item_type | file |
+ # | item_source | A_NUMBER |
+ # | share_type | 0 |
+ # | file_source | A_NUMBER |
+ # | path | /textfile0 (2).txt |
+ # | permissions | 19 |
+ # | stime | A_NUMBER |
+ # | storage | A_NUMBER |
+ # | mail_send | 1 |
+ # | uid_owner | user1 |
+ # | file_parent | A_NUMBER |
+ # | displayname_owner | user1 |
+ # | share_with | user2 |
+ # | share_with_displayname | user2 |
Scenario: Overwrite a federated shared file as recipient
Given Using server "REMOTE"
And user "user1" exists
And user "user2" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
And User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
And User "user1" from server "REMOTE" accepts last pending share
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
And User "user1" modifies text of "/textfile0.txt" with text "BLABLABLA"
When User "user1" uploads file "../../data/user1/files/textfile0.txt" to "/textfile0 (2).txt"
@@ -236,16 +241,16 @@ Feature: federated
Given Using server "REMOTE"
And user "user1" exists
And user "user2" exists
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And user "user0" exists
And User "user0" from server "LOCAL" shares "/PARENT" with user "user1" from server "REMOTE"
And User "user1" from server "REMOTE" accepts last pending share
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
And User "user1" modifies text of "/textfile0.txt" with text "BLABLABLA"
- #When User "user1" uploads file "../../data/user1/files/textfile0.txt" to "/PARENT (2)/textfile0.txt"
- #And Downloading file "/PARENT (2)/textfile0.txt" with range "bytes=0-8"
- #Then Downloaded content should be "BLABLABLA"
+ When User "user1" uploads file "../../data/user1/files/textfile0.txt" to "/PARENT (2)/textfile0.txt"
+ And Downloading file "/PARENT (2)/textfile0.txt" with range "bytes=0-8"
+ Then Downloaded content should be "BLABLABLA"
Scenario: List federated share from another server not accepted yet
Given Using server "LOCAL"
@@ -256,7 +261,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
When As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
Then the list of returned shares has 0 shares
@@ -270,7 +275,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
When As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
@@ -296,7 +301,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And remote server is stopped
When As an "user0"
@@ -318,7 +323,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
# Checking that the file exists caches the file entry, which causes an
# exception to be thrown when getting the file info if the remote server is
@@ -335,8 +340,6 @@ Feature: federated
| user | user0 |
| mountpoint | /remote-share.txt |
-
-
Scenario: Delete federated share with another server
Given Using server "LOCAL"
And user "user0" exists
@@ -349,13 +352,13 @@ Feature: federated
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 1 shares
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And as "user0" the file "/remote-share.txt" exists
And As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
And the list of returned shares has 1 shares
- And Using server "REMOTE"
+ Given Using server "REMOTE"
When As an "user1"
And Deleting last share
Then the OCS status code should be "100"
@@ -363,7 +366,7 @@ Feature: federated
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 0 shares
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And as "user0" the file "/remote-share.txt" does not exist
And As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
@@ -381,7 +384,7 @@ Feature: federated
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 1 shares
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And as "user0" the file "/remote-share.txt" exists
And As an "user0"
@@ -394,7 +397,7 @@ Feature: federated
And As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
And the list of returned shares has 0 shares
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 0 shares
@@ -408,7 +411,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And as "user0" the file "/remote-share.txt" exists
And As an "user0"
@@ -435,7 +438,7 @@ Feature: federated
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 1 shares
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And as "user0" the file "/remote-share.txt" exists
And As an "user0"
@@ -447,7 +450,7 @@ Feature: federated
And As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
And the list of returned shares has 0 shares
- And Using server "REMOTE"
+ Given Using server "REMOTE"
And As an "user1"
And sending "GET" to "/apps/files_sharing/api/v1/shares"
And the list of returned shares has 0 shares
@@ -461,7 +464,7 @@ Feature: federated
# server may have its own /textfile0.txt" file)
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
- And Using server "LOCAL"
+ Given Using server "LOCAL"
And User "user0" from server "LOCAL" accepts last pending share
And as "user0" the file "/remote-share.txt" exists
And As an "user0"
@@ -474,3 +477,115 @@ Feature: federated
And As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
And the list of returned shares has 0 shares
+
+ Scenario: Share to a non-trusted server will NOT auto accept
+ Given Using server "LOCAL"
+ And user "user0" exists
+ Given Using server "REMOTE"
+ And user "userfed2" exists
+ And parameter "federated_trusted_share_auto_accept" of app "files_sharing" is set to "yes"
+ When As an "user0"
+ When User "user0" from server "LOCAL" shares "/textfile0.txt" with user "userfed2" from server "REMOTE"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=false"
+ And the list of returned shares has 1 shares
+ Given Using server "REMOTE"
+ And using new dav path
+ And As an "userfed2"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 0 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 1 shares
+ And as "userfed2" the file "/textfile0 (2).txt" does not exist
+
+ Scenario: Share to a non-trusted server group will NOT auto accept
+ Given Using server "REMOTE"
+ And parameter "incoming_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
+ And parameter "federated_trusted_share_auto_accept" of app "files_sharing" is set to "yes"
+ And user "gs-userfed3" exists
+ And user "gs-userfed4" exists
+ And group "groupfed2" exists
+ And As an "admin"
+ And Add user "gs-userfed3" to the group "groupfed2"
+ And Add user "gs-userfed4" to the group "groupfed2"
+ Given Using server "LOCAL"
+ And parameter "outgoing_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
+ And user "gs-user0" exists
+ When As an "gs-user0"
+ When User "gs-user0" from server "LOCAL" shares "/textfile0.txt" with group "groupfed2" from server "REMOTE"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=false"
+ And the list of returned shares has 1 shares
+ Given Using server "REMOTE"
+ And using new dav path
+ And As an "gs-userfed3"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 0 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 1 shares
+ And as "gs-userfed3" the file "/textfile0 (2).txt" does not exist
+ And As an "gs-userfed4"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 0 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 1 shares
+ And as "gs-userfed4" the file "/textfile0 (2).txt" does not exist
+
+ @TrustedFederation
+ Scenario: Share to a trusted server auto accept
+ Given Using server "LOCAL"
+ And user "user0" exists
+ Given Using server "REMOTE"
+ And user "userfed1" exists
+ And parameter "federated_trusted_share_auto_accept" of app "files_sharing" is set to "yes"
+ When As an "user0"
+ When User "user0" from server "LOCAL" shares "/textfile0.txt" with user "userfed1" from server "REMOTE"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=false"
+ And the list of returned shares has 1 shares
+ Given Using server "REMOTE"
+ And using new dav path
+ And As an "userfed1"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 1 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 0 shares
+ And as "userfed1" the file "/textfile0 (2).txt" exists
+
+ @TrustedFederation
+ Scenario: Share to a trusted server group auto accept
+ Given Using server "REMOTE"
+ And parameter "incoming_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
+ And parameter "federated_trusted_share_auto_accept" of app "files_sharing" is set to "yes"
+ And user "gs-userfed1" exists
+ And user "gs-userfed2" exists
+ And group "groupfed1" exists
+ And As an "admin"
+ And Add user "gs-userfed1" to the group "groupfed1"
+ And Add user "gs-userfed2" to the group "groupfed1"
+ Given Using server "LOCAL"
+ And parameter "outgoing_server2server_group_share_enabled" of app "files_sharing" is set to "yes"
+ And user "gs-user0" exists
+ When As an "gs-user0"
+ When User "gs-user0" from server "LOCAL" shares "/textfile0.txt" with group "groupfed1" from server "REMOTE"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=false"
+ And the list of returned shares has 1 shares
+ Given Using server "REMOTE"
+ And using new dav path
+ And As an "gs-userfed1"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 1 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 0 shares
+ And as "gs-userfed1" the file "/textfile0 (2).txt" exists
+ And As an "gs-userfed2"
+ And sending "GET" to "/apps/files_sharing/api/v1/remote_shares"
+ And the list of returned shares has 1 shares
+ When sending "GET" to "/apps/files_sharing/api/v1/remote_shares/pending"
+ And the list of returned shares has 0 shares
+ And as "gs-userfed2" the file "/textfile0 (2).txt" exists
diff --git a/build/integration/file_conversions/file_conversions.feature b/build/integration/file_conversions/file_conversions.feature
new file mode 100644
index 00000000000..92dc11a647a
--- /dev/null
+++ b/build/integration/file_conversions/file_conversions.feature
@@ -0,0 +1,122 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+Feature: conversions
+ Background:
+ Given using api version "2"
+ Given using new dav path
+ Given user "user0" exists
+
+ Scenario: Converting a file works
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ Then as "user0" the file "/image.jpg" exists
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/image.png" exists
+
+ Scenario: Converting a file to a given path works
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And User "user0" created a folder "/folder"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the folder "/folder" exists
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/folder/image.png" exists
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Converting a file path with overwrite
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And user "user0" uploads file "data/green-square-256.png" to "/image.png"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" exists
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" exists
+ Then as "user0" the file "/image (2).png" exists
+
+ Scenario: Converting a file path with overwrite to a given path
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And User "user0" created a folder "/folder"
+ And user "user0" uploads file "data/green-square-256.png" to "/folder/image.png"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the folder "/folder" exists
+ Then as "user0" the file "/folder/image.png" exists
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/folder/image.png" exists
+ Then as "user0" the file "/folder/image (2).png" exists
+ Then as "user0" the file "/image.png" does not exist
+ Then as "user0" the file "/image.jpg" exists
+
+ Scenario: Converting a file which does not exist fails
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "404"
+ Then the OCS status code should be "404"
+ Then as "user0" the file "/image.jpg" does not exist
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Converting a file to an invalid destination path fails
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "404"
+ Then the OCS status code should be "404"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/folder/image.png" does not exist
+
+ Scenario: Converting a file to an invalid format fails
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ When user "user0" converts file "/image.jpg" to "image/invalid"
+ Then the HTTP status code should be "500"
+ Then the OCS status code should be "999"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" does not exist
+
+Scenario: Converting a file to a given path without extension fails
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And User "user0" created a folder "/folder"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the folder "/folder" exists
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image"
+ Then the HTTP status code should be "400"
+ Then the OCS status code should be "400"
+ Then as "user0" the file "/folder/image.png" does not exist
+ Then as "user0" the file "/image.png" does not exist
+
+ @local_storage
+ Scenario: Converting a file bigger than 100 MiB fails
+ Given file "/image.jpg" of size 108003328 is created in local storage
+ Then as "user0" the folder "/local_storage" exists
+ Then as "user0" the file "/local_storage/image.jpg" exists
+ When user "user0" converts file "/local_storage/image.jpg" to "image/png" and saves it to "/image.png"
+ Then the HTTP status code should be "400"
+ Then the OCS status code should be "400"
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Forbid conversion to a destination without create permission
+ Given user "user1" exists
+ # Share the folder with user1
+ Given User "user0" created a folder "/folder"
+ Then As an "user0"
+ When creating a share with
+ | path | folder |
+ | shareWith | user1 |
+ | shareType | 0 |
+ | permissions | 1 |
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ # Create the folder, upload the image
+ Then As an "user1"
+ Given user "user1" accepts last share
+ Given as "user1" the folder "/folder" exists
+ Given user "user1" uploads file "data/clouds.jpg" to "/image.jpg"
+ Then as "user1" the file "/image.jpg" exists
+ # Try to convert the image to a folder where user1 has no create permission
+ When user "user1" converts file "/image.jpg" to "image/png" and saves it to "/folder/folder.png"
+ Then the OCS status code should be "403"
+ And the HTTP status code should be "403"
+ Then as "user1" the file "/folder/folder.png" does not exist
diff --git a/build/integration/files_features/metadata.feature b/build/integration/files_features/metadata.feature
new file mode 100644
index 00000000000..553a7b62306
--- /dev/null
+++ b/build/integration/files_features/metadata.feature
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+Feature: metadata
+
+ Scenario: Setting metadata works
+ Given user "user0" exists
+ When User "user0" uploads file with content "AAA" to "/test.txt"
+ And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
+ Then User "user0" should see the prop "metadata-files-live-photo" equal to "metadata-value" for file "/test.txt"
+
+ Scenario: Deleting metadata works
+ Given user "user0" exists
+ When User "user0" uploads file with content "AAA" to "/test.txt"
+ And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
+ And User "user0" deletes the "metadata-files-live-photo" prop on "/test.txt"
+ Then User "user0" should not see the prop "metadata-files-live-photo" for file "/test.txt"
diff --git a/build/integration/files_features/transfer-ownership.feature b/build/integration/files_features/transfer-ownership.feature
index 34fed8b9efd..6f7a7944166 100644
--- a/build/integration/files_features/transfer-ownership.feature
+++ b/build/integration/files_features/transfer-ownership.feature
@@ -184,10 +184,10 @@ Feature: transfer-ownership
And As an "user2"
Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
+ And as "user1" the folder "/test" exists
+ And As an "user1"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -210,13 +210,12 @@ Feature: transfer-ownership
And user "user1" accepts last share
When transferring ownership from "user0" to "user1"
And the command was successful
- And As an "user1"
- Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ Then as "user0" the folder "/test" does not exist
+ When As an "user1"
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user1"
+ Then as "user1" the folder "/test" exists
+ And Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -242,10 +241,10 @@ Feature: transfer-ownership
And As an "user2"
Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
+ And as "user1" the folder "/test" exists
+ And As an "user1"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -253,7 +252,7 @@ Feature: transfer-ownership
| uid_file_owner | user3 |
| share_with | group1 |
- Scenario: transferring ownership does not transfer received shares
+ Scenario: transferring ownership transfers received shares
Given user "user0" exists
And user "user1" exists
And user "user2" exists
@@ -264,16 +263,16 @@ Feature: transfer-ownership
And the command was successful
And As an "user1"
And using received transfer folder of "user1" as dav path
- Then as "user1" the folder "/test" does not exist
+ Then as "user1" the folder "/test" exists
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And As an "user2"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
| uid_owner | user2 |
| uid_file_owner | user2 |
- | share_with | user0 |
+ | share_with | user1 |
@local_storage
Scenario: transferring ownership does not transfer external storage
@@ -514,27 +513,7 @@ Feature: transfer-ownership
And user "user2" accepts last share
When transferring ownership of path "test" from "user0" to "user1"
Then the command failed with exit code 1
- And the command output contains the text "Could not transfer files."
-
- Scenario: transferring ownership does not transfer received shares
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And User "user2" created a folder "/test"
- And User "user0" created a folder "/sub"
- And folder "/test" of user "user2" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And User "user0" moved folder "/test" to "/sub/test"
- When transferring ownership of path "sub" from "user0" to "user1"
- And the command was successful
- And As an "user1"
- And using received transfer folder of "user1" as dav path
- Then as "user1" the folder "/sub" exists
- And as "user1" the folder "/sub/test" does not exist
- And using old dav path
- And as "user0" the folder "/sub" does not exist
- And Getting info of last share
- And the OCS status code should be "404"
+ And the command error output contains the text "Moving a storage (user0/files/test) into another storage (user1) is not allowed"
Scenario: transferring ownership transfers received shares into subdir when requested
Given user "user0" exists
@@ -548,7 +527,7 @@ Feature: transfer-ownership
And User "user0" moved folder "/transfer-share" to "/sub/transfer-share"
And folder "/do-not-transfer" of user "user2" is shared with user "user0" with permissions 31
And user "user0" accepts last share
- When transferring ownership of path "sub" from "user0" to "user1" with received shares
+ When transferring ownership of path "sub" from "user0" to "user1"
And the command was successful
And As an "user1"
And using received transfer folder of "user1" as dav path
diff --git a/build/integration/files_features/windows_compatibility.feature b/build/integration/files_features/windows_compatibility.feature
new file mode 100644
index 00000000000..feaaca1ed3a
--- /dev/null
+++ b/build/integration/files_features/windows_compatibility.feature
@@ -0,0 +1,68 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+Feature: Windows compatible filenames
+ Background:
+ Given using api version "1"
+ And using new dav path
+ And As an "admin"
+
+ Scenario: prevent upload files with invalid name
+ Given As an "admin"
+ And user "user0" exists
+ And invoking occ with "files:windows-compatible-filenames --enable"
+ Given User "user0" created a folder "/com1"
+ Then as "user0" the file "/com1" does not exist
+
+ Scenario: renaming a folder with invalid name
+ Given As an "admin"
+ When invoking occ with "files:windows-compatible-filenames --disable"
+ And user "user0" exists
+ Given User "user0" created a folder "/aux"
+ When invoking occ with "files:windows-compatible-filenames --enable"
+ And invoking occ with "files:sanitize-filenames user0"
+ Then as "user0" the file "/aux" does not exist
+ And as "user0" the file "/aux (renamed)" exists
+
+ Scenario: renaming a file with invalid base name
+ Given As an "admin"
+ When invoking occ with "files:windows-compatible-filenames --disable"
+ And user "user0" exists
+ When User "user0" uploads file with content "hello" to "/com0.txt"
+ And invoking occ with "files:windows-compatible-filenames --enable"
+ And invoking occ with "files:sanitize-filenames user0"
+ Then as "user0" the file "/com0.txt" does not exist
+ And as "user0" the file "/com0 (renamed).txt" exists
+
+ Scenario: renaming a file with invalid extension
+ Given As an "admin"
+ When invoking occ with "files:windows-compatible-filenames --disable"
+ And user "user0" exists
+ When User "user0" uploads file with content "hello" to "/foo.txt."
+ And as "user0" the file "/foo.txt." exists
+ And invoking occ with "files:windows-compatible-filenames --enable"
+ And invoking occ with "files:sanitize-filenames user0"
+ Then as "user0" the file "/foo.txt." does not exist
+ And as "user0" the file "/foo.txt" exists
+
+ Scenario: renaming a file with invalid character
+ Given As an "admin"
+ When invoking occ with "files:windows-compatible-filenames --disable"
+ And user "user0" exists
+ When User "user0" uploads file with content "hello" to "/2*2=4.txt"
+ And as "user0" the file "/2*2=4.txt" exists
+ And invoking occ with "files:windows-compatible-filenames --enable"
+ And invoking occ with "files:sanitize-filenames user0"
+ Then as "user0" the file "/2*2=4.txt" does not exist
+ And as "user0" the file "/2_2=4.txt" exists
+
+ Scenario: renaming a file with invalid character and replacement setup
+ Given As an "admin"
+ When invoking occ with "files:windows-compatible-filenames --disable"
+ And user "user0" exists
+ When User "user0" uploads file with content "hello" to "/2*3=6.txt"
+ And as "user0" the file "/2*3=6.txt" exists
+ And invoking occ with "files:windows-compatible-filenames --enable"
+ And invoking occ with "files:sanitize-filenames --char-replacement + user0"
+ Then as "user0" the file "/2*3=6.txt" does not exist
+ And as "user0" the file "/2+3=6.txt" exists
diff --git a/build/integration/filesdrop_features/filesdrop.feature b/build/integration/filesdrop_features/filesdrop.feature
index 2c9156dea02..7618a31a1d0 100644
--- a/build/integration/filesdrop_features/filesdrop.feature
+++ b/build/integration/filesdrop_features/filesdrop.feature
@@ -33,7 +33,7 @@ Feature: FilesDrop
And Downloading file "/drop/a (2).txt"
Then Downloaded content should be "def"
- Scenario: Files drop ignores directory
+ Scenario: Files drop forbid directory without a nickname
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
@@ -44,10 +44,9 @@ Feature: FilesDrop
And Updating last share with
| permissions | 4 |
When Dropping file "/folder/a.txt" with "abc"
- And Downloading file "/drop/a.txt"
- Then Downloaded content should be "abc"
+ Then the HTTP status code should be "400"
- Scenario: Files drop forbis MKCOL
+ Scenario: Files drop forbid MKCOL without a nickname
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
@@ -58,7 +57,33 @@ Feature: FilesDrop
And Updating last share with
| permissions | 4 |
When Creating folder "folder" in drop
- Then the HTTP status code should be "405"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files drop allows MKCOL with a nickname
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 3 |
+ | publicUpload | true |
+ And Updating last share with
+ | permissions | 4 |
+ When Creating folder "folder" in drop as "nickname"
+ Then the HTTP status code should be "201"
+
+ Scenario: Files drop forbid subfolder creation without a nickname
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 3 |
+ | publicUpload | true |
+ And Updating last share with
+ | permissions | 4 |
+ When dropping file "/folder/a.txt" with "abc"
+ Then the HTTP status code should be "400"
Scenario: Files request drop
Given user "user0" exists
@@ -71,9 +96,50 @@ Feature: FilesDrop
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as "Alice"
- And Downloading file "/drop/Alice/a.txt"
+ And Downloading file "/drop/Alice/folder/a.txt"
+ Then Downloaded content should be "abc"
+
+ Scenario: File drop uploading folder with name of file
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder" with "its a file" as "Alice"
+ Then the HTTP status code should be "201"
+ When Dropping file "/folder/a.txt" with "abc" as "Alice"
+ Then the HTTP status code should be "201"
+ When Downloading file "/drop/Alice/folder"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "its a file"
+ When Downloading file "/drop/Alice/folder (2)/a.txt"
Then Downloaded content should be "abc"
+ Scenario: File drop uploading file with name of folder
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as "Alice"
+ Then the HTTP status code should be "201"
+ When Dropping file "/folder" with "its a file" as "Alice"
+ Then the HTTP status code should be "201"
+ When Downloading file "/drop/Alice/folder/a.txt"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "abc"
+ When Downloading file "/drop/Alice/folder (2)"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "its a file"
+
Scenario: Put file same file multiple times via files drop
Given user "user0" exists
And As an "user0"
@@ -86,7 +152,86 @@ Feature: FilesDrop
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as "Mallory"
And Dropping file "/folder/a.txt" with "def" as "Mallory"
- And Downloading file "/drop/Mallory/a.txt"
+ # Ensure folder structure and that we only checked
+ # for files duplicates, but merged the existing folders
+ Then as "user0" the folder "/drop/Mallory" exists
+ Then as "user0" the folder "/drop/Mallory/folder" exists
+ Then as "user0" the folder "/drop/Mallory (2)" does not exist
+ Then as "user0" the folder "/drop/Mallory/folder (2)" does not exist
+ Then as "user0" the file "/drop/Mallory/folder/a.txt" exists
+ Then as "user0" the file "/drop/Mallory/folder/a (2).txt" exists
+ And Downloading file "/drop/Mallory/folder/a.txt"
Then Downloaded content should be "abc"
- And Downloading file "/drop/Mallory/a (2).txt"
+ And Downloading file "/drop/Mallory/folder/a (2).txt"
Then Downloaded content should be "def"
+
+ Scenario: Files drop prevents GET
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | shareWith | |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ When Dropping file "/folder/a.txt" with "abc" as "Mallory"
+ When as "user0" the file "/drop/Mallory/folder/a.txt" exists
+ And Downloading public folder "Mallory"
+ Then the HTTP status code should be "405"
+ And Downloading public folder "Mallory/folder"
+ Then the HTTP status code should be "405"
+ And Downloading public file "Mallory/folder/a.txt"
+ Then the HTTP status code should be "405"
+
+ Scenario: Files drop requires nickname if file request is enabled
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with slashes
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as "Alice/Bob/Mallory"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with forbidden characters
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as ".htaccess"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with forbidden characters
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as ".Mallory"
+ Then the HTTP status code should be "400"
diff --git a/build/integration/openldap_features/openldap-uid-username.feature b/build/integration/openldap_features/openldap-uid-username.feature
index 9d5405e88bf..bee4098972b 100644
--- a/build/integration/openldap_features/openldap-uid-username.feature
+++ b/build/integration/openldap_features/openldap-uid-username.feature
@@ -151,6 +151,7 @@ Feature: LDAP
| ldapAttributesForUserSearch | employeeNumber |
| useMemberOfToDetectMembership | 1 |
And parameter "shareapi_only_share_with_group_members" of app "core" is set to "yes"
+ And invoking occ with "ldap:check-group cn=Orcharding,ou=OtherGroups,dc=nextcloud,dc=ci --update"
And As an "alice"
When getting sharees for
# "5" is part of the employee number of some LDAP records
@@ -162,4 +163,3 @@ Feature: LDAP
And "users" sharees returned are
| Elisa | 0 | elisa |
And "exact groups" sharees returned is empty
-
diff --git a/build/integration/features/ratelimiting.feature b/build/integration/ratelimiting_features/ratelimiting.feature
index a2fca2fc6be..43cfddec85d 100644
--- a/build/integration/features/ratelimiting.feature
+++ b/build/integration/ratelimiting_features/ratelimiting.feature
@@ -1,5 +1,6 @@
# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
+@RateLimiting
Feature: ratelimiting
Background:
diff --git a/build/integration/routing_features/apps-and-routes.feature b/build/integration/routing_features/apps-and-routes.feature
new file mode 100644
index 00000000000..954ea73bfac
--- /dev/null
+++ b/build/integration/routing_features/apps-and-routes.feature
@@ -0,0 +1,52 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: appmanagement
+ Background:
+ Given using api version "2"
+ And user "user1" exists
+ And user "user2" exists
+ And group "group1" exists
+ And user "user1" belongs to group "group1"
+
+ Scenario: Enable app and test route
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "404"
+ When sending "POST" to "/cloud/apps/weather_status"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ And app "weather_status" is enabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "user1"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "user2"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+
+ Scenario: Enable app only for some groups
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "404"
+ Given invoking occ with "app:enable weather_status --groups group1"
+ Then the command was successful
+ Given As an "user2"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the HTTP status code should be "412"
+ Given As an "user1"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
diff --git a/build/integration/run.sh b/build/integration/run.sh
index 309f8ed0c75..30dd0646b10 100755
--- a/build/integration/run.sh
+++ b/build/integration/run.sh
@@ -18,10 +18,18 @@ HIDE_OC_LOGS=$2
INSTALLED=$($OCC status | grep installed: | cut -d " " -f 5)
if [ "$INSTALLED" == "true" ]; then
+ # Disable appstore to avoid spamming from CI
+ $OCC config:system:set appstoreenabled --value=false --type=boolean
# Disable bruteforce protection because the integration tests do trigger them
$OCC config:system:set auth.bruteforce.protection.enabled --value false --type bool
+ # Disable rate limit protection because the integration tests do trigger them
+ $OCC config:system:set ratelimit.protection.enabled --value false --type bool
# Allow local remote urls otherwise we can not share
$OCC config:system:set allow_local_remote_servers --value true --type bool
+ # Allow self signed certificates
+ $OCC config:system:set sharing.federation.allowSelfSignedCertificates --value true --type bool
+ # Allow creating users with dummy passwords
+ $OCC app:disable password_policy
else
if [ "$SCENARIO_TO_RUN" != "setup_features/setup.feature" ]; then
echo "Nextcloud instance needs to be installed" >&2
@@ -38,6 +46,7 @@ if [ -z "$EXECUTOR_NUMBER" ]; then
fi
PORT=$((8080 + $EXECUTOR_NUMBER))
echo $PORT
+export PORT
echo "" > "${NC_DATADIR}/nextcloud.log"
echo "" > phpserver.log
diff --git a/build/integration/sharing_features/sharing-activity.feature b/build/integration/sharing_features/sharing-activity.feature
new file mode 100644
index 00000000000..016b376488b
--- /dev/null
+++ b/build/integration/sharing_features/sharing-activity.feature
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: sharing
+ Background:
+ Given using api version "1"
+ Given using new dav path
+ Given invoking occ with "app:enable --force activity"
+ Given the command was successful
+ Given user "user0" exists
+ And Logging in using web as "user0"
+ And Sending a "POST" to "/apps/activity/settings" with requesttoken
+ | public_links_notification | 1 |
+ | public_links_upload_notification | 1 |
+ | notify_setting_batchtime | 0 |
+ | activity_digest | 0 |
+
+ Scenario: Creating a new mail share and check activity
+ Given dummy mail server is listening
+ And As an "user0"
+ When creating a share with
+ | path | welcome.txt |
+ | shareType | 4 |
+ | shareWith | dumy@test.com |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share can be downloaded
+ Then last activity should be
+ | app | files_sharing |
+ | type | public_links |
+ | object_type | files |
+ | object_name | /welcome.txt |
+
+ Scenario: Creating a new public share and check activity
+ Given user "user0" exists
+ And As an "user0"
+ When creating a share with
+ | path | welcome.txt |
+ | shareType | 3 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last link share can be downloaded
+ Then last activity should be
+ | app | files_sharing |
+ | type | public_links |
+ | object_type | files |
+ | object_name | /welcome.txt |
diff --git a/build/integration/sharing_features/sharing-v1-part2.feature b/build/integration/sharing_features/sharing-v1-part2.feature
index 8cc97fe71ee..a6e4c67165a 100644
--- a/build/integration/sharing_features/sharing-v1-part2.feature
+++ b/build/integration/sharing_features/sharing-v1-part2.feature
@@ -543,6 +543,29 @@ Feature: sharing
And the HTTP status code should be "200"
And last share_id is included in the answer
+ Scenario: Group shares are deleted when the group is deleted
+ Given As an "admin"
+ And user "user0" exists
+ And user "user1" exists
+ And group "group0" exists
+ And user "user0" belongs to group "group0"
+ And file "textfile0.txt" of user "user1" is shared with group "group0"
+ And As an "user0"
+ When sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=true"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share_id is included in the answer
+ When group "group0" does not exist
+ Then sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=true"
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share_id is not included in the answer
+ When group "group0" exists
+ Then sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=true"
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share_id is not included in the answer
+
Scenario: User is not allowed to reshare file
As an "admin"
Given user "user0" exists
@@ -701,6 +724,79 @@ Feature: sharing
Then the OCS status code should be "404"
And the HTTP status code should be "200"
+ Scenario: download restrictions can not be dropped
+ As an "admin"
+ Given user "user0" exists
+ And user "user1" exists
+ And user "user2" exists
+ And User "user0" uploads file with content "foo" to "/tmp.txt"
+ And As an "user0"
+ And creating a share with
+ | path | /tmp.txt |
+ | shareType | 0 |
+ | shareWith | user1 |
+ | permissions | 17 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+ And As an "user1"
+ And accepting last share
+ When Getting info of last share
+ Then Share fields of last share match with
+ | uid_owner | user0 |
+ | uid_file_owner | user0 |
+ | permissions | 17 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+ When creating a share with
+ | path | /tmp.txt |
+ | shareType | 0 |
+ | shareWith | user2 |
+ | permissions | 1 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When As an "user2"
+ And accepting last share
+ And Getting info of last share
+ Then Share fields of last share match with
+ | share_type | 0 |
+ | permissions | 1 |
+ | uid_owner | user1 |
+ | uid_file_owner | user0 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+
+ Scenario: download restrictions can not be dropped when re-sharing even on link shares
+ As an "admin"
+ Given user "user0" exists
+ And user "user1" exists
+ And User "user0" uploads file with content "foo" to "/tmp.txt"
+ And As an "user0"
+ And creating a share with
+ | path | /tmp.txt |
+ | shareType | 0 |
+ | shareWith | user1 |
+ | permissions | 17 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+ And As an "user1"
+ And accepting last share
+ When Getting info of last share
+ Then Share fields of last share match with
+ | uid_owner | user0 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+ When creating a share with
+ | path | /tmp.txt |
+ | shareType | 3 |
+ | permissions | 1 |
+ And Getting info of last share
+ And Updating last share with
+ | hideDownload | false |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When Getting info of last share
+ Then Share fields of last share match with
+ | share_type | 3 |
+ | uid_owner | user1 |
+ | uid_file_owner | user0 |
+ | hide_download | 1 |
+ | attributes | [{"scope":"permissions","key":"download","value":false}] |
+
Scenario: User is not allowed to reshare file with additional delete permissions
As an "admin"
Given user "user0" exists
@@ -1169,7 +1265,9 @@ Feature: sharing
|{http://open-collaboration-services.org/ns}share-permissions |
Then the single response should contain a property "{http://open-collaboration-services.org/ns}share-permissions" with value "19"
- Scenario: Cannot download a file when it's shared view-only
+ Scenario: Cannot download a file when it's shared view-only without shareapi_allow_view_without_download
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" moves file "/textfile0.txt" to "/document.odt"
@@ -1178,8 +1276,15 @@ Feature: sharing
When As an "user1"
And Downloading file "/document.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And Downloading file "/document.odt"
+ Then the HTTP status code should be "200"
- Scenario: Cannot download a file when its parent is shared view-only
+ Scenario: Cannot download a file when its parent is shared view-only without shareapi_allow_view_without_download
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" created a folder "/sharedviewonly"
@@ -1189,8 +1294,15 @@ Feature: sharing
When As an "user1"
And Downloading file "/sharedviewonly/document.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And Downloading file "/sharedviewonly/document.odt"
+ Then the HTTP status code should be "200"
- Scenario: Cannot copy a file when it's shared view-only
+ Scenario: Cannot copy a file when it's shared view-only even with shareapi_allow_view_without_download enabled
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" moves file "/textfile0.txt" to "/document.odt"
@@ -1198,8 +1310,15 @@ Feature: sharing
And user "user1" accepts last share
When User "user1" copies file "/document.odt" to "/copyforbidden.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And User "user1" copies file "/document.odt" to "/copyforbidden.odt"
+ Then the HTTP status code should be "403"
Scenario: Cannot copy a file when its parent is shared view-only
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" created a folder "/sharedviewonly"
@@ -1208,5 +1327,10 @@ Feature: sharing
And user "user1" accepts last share
When User "user1" copies file "/sharedviewonly/document.odt" to "/copyforbidden.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And User "user1" copies file "/sharedviewonly/document.odt" to "/copyforbidden.odt"
+ Then the HTTP status code should be "403"
# See sharing-v1-part3.feature
diff --git a/build/integration/sharing_features/sharing-v1-part4.feature b/build/integration/sharing_features/sharing-v1-part4.feature
index 1b32f99a0a2..d138f0a1769 100644
--- a/build/integration/sharing_features/sharing-v1-part4.feature
+++ b/build/integration/sharing_features/sharing-v1-part4.feature
@@ -124,3 +124,61 @@ Scenario: Receiving a share of a file without delete permission gives delete per
| path | /welcome (2).txt |
| permissions | 19 |
| item_permissions | 27 |
+
+# This is a regression test as in the past creating a file drop required creating with permissions=5
+# and then afterwards update the share to permissions=4
+Scenario: Directly create link share with CREATE only permissions (file drop)
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/TMP"
+ When creating a share with
+ | path | TMP |
+ | shareType | 3 |
+ | permissions | 4 |
+ And Getting info of last share
+ Then Share fields of last share match with
+ | uid_file_owner | user0 |
+ | share_type | 3 |
+ | permissions | 4 |
+
+Scenario: Directly create email share with CREATE only permissions (file drop)
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/TMP"
+ When creating a share with
+ | path | TMP |
+ | shareType | 4 |
+ | shareWith | j.doe@example.com |
+ | permissions | 4 |
+ And Getting info of last share
+ Then Share fields of last share match with
+ | uid_file_owner | user0 |
+ | share_type | 4 |
+ | permissions | 4 |
+
+# This ensures the legacy behavior of sharing v1 is kept
+Scenario: publicUpload overrides permissions
+ Given user "user0" exists
+ And As an "user0"
+ And parameter "outgoing_server2server_share_enabled" of app "files_sharing" is set to "no"
+ And user "user0" created a folder "/TMP"
+ When creating a share with
+ | path | TMP |
+ | shareType | 3 |
+ | permissions | 4 |
+ | publicUpload | true |
+ And Getting info of last share
+ Then Share fields of last share match with
+ | uid_file_owner | user0 |
+ | share_type | 3 |
+ | permissions | 15 |
+ When creating a share with
+ | path | TMP |
+ | shareType | 3 |
+ | permissions | 4 |
+ | publicUpload | false |
+ And Getting info of last share
+ Then Share fields of last share match with
+ | uid_file_owner | user0 |
+ | share_type | 3 |
+ | permissions | 1 |
diff --git a/build/integration/sharing_features/sharing-v1.feature b/build/integration/sharing_features/sharing-v1.feature
index 708667f123c..25f168db2e7 100644
--- a/build/integration/sharing_features/sharing-v1.feature
+++ b/build/integration/sharing_features/sharing-v1.feature
@@ -231,10 +231,11 @@ Feature: sharing
| url | AN_URL |
| mimetype | httpd/unix-directory |
- Scenario: Creating a new share with expiration date removed, when default expiration is set
+ Scenario: Creating a new share with expiration date empty, when default expiration is set
Given user "user0" exists
And user "user1" exists
- And parameter "shareapi_default_expire_date" of app "core" is set to "yes"
+ And parameter "shareapi_default_internal_expire_date" of app "core" is set to "yes"
+ And parameter "shareapi_internal_expire_after_n_days" of app "core" is set to "3"
And As an "user0"
When creating a share with
| path | welcome.txt |
@@ -249,6 +250,43 @@ Feature: sharing
And Share fields of last share match with
| expiration ||
+ Scenario: Creating a new share with expiration date removed, when default expiration is set
+ Given user "user0" exists
+ And user "user1" exists
+ And parameter "shareapi_default_internal_expire_date" of app "core" is set to "yes"
+ And parameter "shareapi_internal_expire_after_n_days" of app "core" is set to "3"
+ And As an "user0"
+ When creating a share with
+ | path | welcome.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And Getting info of last share
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And Share fields of last share match with
+ | expiration | +3 days |
+
+ Scenario: Creating a new share with expiration date null, when default expiration is set
+ Given user "user0" exists
+ And user "user1" exists
+ And parameter "shareapi_default_internal_expire_date" of app "core" is set to "yes"
+ And parameter "shareapi_internal_expire_after_n_days" of app "core" is set to "3"
+ And As an "user0"
+ When creating a share with
+ | path | welcome.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ | expireDate | null |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And Getting info of last share
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And Share fields of last share match with
+ | expiration | +3 days |
+
Scenario: Creating a new public share, updating its password and getting its info
Given user "user0" exists
And As an "user0"
diff --git a/build/integration/theming_features/theming.feature b/build/integration/theming_features/theming.feature
new file mode 100644
index 00000000000..2ae5d4f75c3
--- /dev/null
+++ b/build/integration/theming_features/theming.feature
@@ -0,0 +1,131 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: theming
+
+ Background:
+ Given user "user0" exists
+
+ Scenario: themed stylesheets are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ # Themes that can not be explicitly set by a guest could have been
+ # globally set too through "enforce_theme".
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for guests
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for disabled users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"