diff --git a/.codacy.yml b/.codacy.yml index c9dd79b0bd5..1d94123ca78 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -16,5 +16,5 @@ exclude_paths: - 'tests/acceptance/expected-failures-*.md' - 'tests/acceptance/features/bootstrap/**' - 'tests/TestHelpers/**' - + - 'tests/acceptance/run.sh' ... diff --git a/.drone.env b/.drone.env index 91e6b95c842..dd887f13f00 100644 --- a/.drone.env +++ b/.drone.env @@ -1,7 +1,3 @@ -# The test runner source for API tests -CORE_COMMITID=3d6c0aafd7710bee6a115c27742a7939b622079e -CORE_BRANCH=master - # The test runner source for UI tests WEB_COMMITID=f2b928be7e6ce85265868660049a4ff15caa74bc WEB_BRANCH=master diff --git a/.drone.star b/.drone.star index b964328bdb1..610b29a852e 100644 --- a/.drone.star +++ b/.drone.star @@ -5,7 +5,6 @@ ALPINE_GIT = "alpine/git:latest" CHKO_DOCKER_PUSHRM = "chko/docker-pushrm:1" DRONE_CLI = "drone/cli:alpine" -MARIADB = "mariadb:10.6" MINIO_MC = "minio/mc:RELEASE.2021-10-07T04-19-58Z" OC_CI_ALPINE = "owncloudci/alpine:latest" OC_CI_BAZEL_BUILDIFIER = "owncloudci/bazel-buildifier:latest" @@ -19,9 +18,7 @@ OC_CI_WAIT_FOR = "owncloudci/wait-for:latest" OC_CS3_API_VALIDATOR = "owncloud/cs3api-validator:0.2.0" OC_LITMUS = "owncloud/litmus:latest" OC_OC_TEST_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.8.3" -OC_SERVER = "owncloud/server:10" OC_UBUNTU = "owncloud/ubuntu:20.04" -OSIXIA_OPEN_LDAP = "osixia/openldap:latest" PLUGINS_CODACY = "plugins/codacy:1" PLUGINS_DOCKER = "plugins/docker:latest" PLUGINS_DOWNSTREAM = "plugins/downstream:latest" @@ -49,11 +46,6 @@ dirs = { "ocisConfig": "tests/config/drone/ocis-config.json", "ocis": "/srv/app/tmp/ocis", "ocisRevaDataRoot": "/srv/app/tmp/ocis/owncloud/data", - # relative path from the base directory dirs["base"] - # this is because the PLUGINS_S3_CACHE does not support absolute paths - # PLUGINS_S3_CACHE is used to cache the core and testing app - "core": "oc10/testrunner", - "testing_app": "oc10/testing", } # configuration @@ -144,24 +136,6 @@ config = { "skip": False, "earlyFail": True, }, - "parallelApiTests": { - "apiSharing": { - "suites": [ - "apiShareManagement", - ], - "skip": True, - "earlyFail": True, - "cron": "nightly", - }, - "apiWebdav": { - "suites": [ - "apiWebdavOperations", - ], - "skip": True, - "earlyFail": True, - "cron": "nightly", - }, - }, "rocketchat": { "channel": "ocis-internal", "from_secret": "private_rocketchat", @@ -253,7 +227,6 @@ def main(ctx): codestyle(ctx) + \ buildWebCache(ctx) + \ [buildOcisBinaryForTesting(ctx)] + \ - cacheCoreReposForTesting(ctx) + \ testOcisModules(ctx) + \ testPipelines(ctx) @@ -384,9 +357,6 @@ def testPipelines(ctx): if "skip" not in config["settingsUITests"] or not config["settingsUITests"]["skip"]: pipelines.append(settingsUITests(ctx)) - if "skip" not in config["parallelApiTests"] or not config["parallelApiTests"]["skip"]: - pipelines += parallelDeployAcceptancePipeline(ctx) - return pipelines def testOcisModule(ctx, module): @@ -474,28 +444,6 @@ def buildOcisBinaryForTesting(ctx): "volumes": [pipelineVolumeGo], } -def cacheCoreReposForTesting(ctx): - return [{ - "kind": "pipeline", - "type": "docker", - "name": "cache_core_repos_for_testing", - "platform": { - "os": "linux", - "arch": "amd64", - }, - "steps": skipIfUnchanged(ctx, "acceptance-tests") + # skip for those pipelines where core repos are not needed - cloneCoreRepos() + - # this does not support the absolute path to the target directory - # so we need to use a relative path :( - rebuildBuildArtifactCache(ctx, "testrunner", dirs["core"]), - "trigger": { - "ref": [ - "refs/heads/master", - "refs/pull/**", - ], - }, - }] - def uploadScanResults(ctx): sonar_env = { "SONAR_TOKEN": { @@ -716,12 +664,10 @@ def localApiTestPipeline(ctx): "steps": skipIfUnchanged(ctx, "acceptance-tests") + restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") + ocisServer(storage, params["accounts_hash_difficulty"], extra_server_environment = params["extraServerEnvironment"]) + - restoreBuildArtifactCache(ctx, "testrunner", dirs["core"]) + localApiTests(suite, storage, params["extraEnvironment"]) + failEarly(ctx, early_fail), "services": redisForOCStorage(storage), - "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]) + - getPipelineNames(cacheCoreReposForTesting(ctx)), + "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ "refs/heads/master", @@ -736,7 +682,6 @@ def localApiTests(suite, storage, extra_environment = {}): environment = { "TEST_WITH_GRAPH_API": "true", "PATH_TO_OCIS": dirs["base"], - "PATH_TO_CORE": "%s/%s" % (dirs["base"], dirs["core"]), "TEST_SERVER_URL": "https://ocis-server:9200", "OCIS_REVA_DATA_ROOT": "%s" % (dirs["ocisRevaDataRoot"] if storage == "owncloud" else ""), "OCIS_SKELETON_STRATEGY": "%s" % ("copy" if storage == "owncloud" else "upload"), @@ -914,7 +859,6 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac "steps": skipIfUnchanged(ctx, "acceptance-tests") + restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") + ocisServer(storage, accounts_hash_difficulty) + - restoreBuildArtifactCache(ctx, "testrunner", dirs["core"]) + [ { "name": "oC10ApiTests-%s-storage-%s" % (storage, part_number), @@ -922,7 +866,6 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac "environment": { "TEST_WITH_GRAPH_API": "true", "PATH_TO_OCIS": "%s" % dirs["base"], - "PATH_TO_CORE": "%s/%s" % (dirs["base"], dirs["core"]), "TEST_SERVER_URL": "https://ocis-server:9200", "OCIS_REVA_DATA_ROOT": "%s" % (dirs["ocisRevaDataRoot"] if storage == "owncloud" else ""), "OCIS_SKELETON_STRATEGY": "%s" % ("copy" if storage == "owncloud" else "upload"), @@ -936,13 +879,12 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac "UPLOAD_DELETE_WAIT_TIME": "1" if storage == "owncloud" else 0, }, "commands": [ - "make -C %s/%s test-acceptance-api" % (dirs["base"], dirs["core"]), + "make -C %s test-acceptance-core-api" % (dirs["base"]), ], }, ] + failEarly(ctx, early_fail), "services": redisForOCStorage(storage), - "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]) + - getPipelineNames(cacheCoreReposForTesting(ctx)), + "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ "refs/heads/master", @@ -2090,70 +2032,6 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "depends_on": depends_on, } - if deploy_type == "parallel": - user = "33:33" - environment = { - # Keycloak IDP specific configuration - "OCIS_OIDC_ISSUER": "https://keycloak/auth/realms/owncloud", - "WEB_OIDC_CLIENT_ID": "ocis-web", - "WEB_OIDC_SCOPE": "openid profile email owncloud", - # external ldap is supposed to be read only - "GRAPH_IDENTITY_BACKEND": "ldap", - "GRAPH_LDAP_SERVER_WRITE_ENABLED": "false", - # LDAP bind - "LDAP_URI": "ldaps://openldap", - "LDAP_INSECURE": "true", - "LDAP_BIND_DN": "cn=admin,dc=owncloud,dc=com", - "LDAP_BIND_PASSWORD": "admin", - # LDAP user settings - "PROXY_USER_OIDC_CLAIM": "ocis.user.uuid", # claim was added in Keycloak - "PROXY_USER_CS3_CLAIM": "userid", # equals STORAGE_LDAP_USER_SCHEMA_UID - "LDAP_GROUP_BASE_DN": "ou=TestGroups,dc=owncloud,dc=com", - "LDAP_GROUP_SCHEMA_ID": "ownclouduuid", - "LDAP_GROUP_FILTER": "(objectclass=owncloud)", - "LDAP_USER_BASE_DN": "ou=TestUsers,dc=owncloud,dc=com", - "LDAP_USER_SCHEMA_ID": "ownclouduuid", - "LDAP_USER_FILTER": "(objectclass=owncloud)", - "LDAP_GROUP_SUBSTRING_FILTER_TYPE": "any", - "LDAP_USER_SUBSTRING_FILTER_TYPE": "any", - # ownCloudSQL storage driver - "STORAGE_USERS_DRIVER": "owncloudsql", - "STORAGE_USERS_OWNCLOUDSQL_DATADIR": "/mnt/data/files", - "STORAGE_USERS_OWNCLOUDSQL_SHARE_FOLDER": "/Shares", - "STORAGE_USERS_OWNCLOUDSQL_LAYOUT": "{{.Username}}", - "STORAGE_USERS_OWNCLOUDSQL_DB_USERNAME": "owncloud", - "STORAGE_USERS_OWNCLOUDSQL_DB_PASSWORD": "owncloud", - "STORAGE_USERS_OWNCLOUDSQL_DB_HOST": "oc10-db", - "STORAGE_USERS_OWNCLOUDSQL_DB_PORT": 3306, - "STORAGE_USERS_OWNCLOUDSQL_DB_NAME": "owncloud", - # ownCloudSQL sharing driver - "SHARING_USER_DRIVER": "owncloudsql", - "SHARING_USER_OWNCLOUDSQL_DB_USERNAME": "owncloud", - "SHARING_USER_OWNCLOUDSQL_DB_PASSWORD": "owncloud", - "SHARING_USER_OWNCLOUDSQL_DB_HOST": "oc10-db", - "SHARING_USER_OWNCLOUDSQL_DB_PORT": 3306, - "SHARING_USER_OWNCLOUDSQL_DB_NAME": "owncloud", - # General oCIS config - # OCIS_RUN_SERVICES specifies to start all fullstack services except idm and idp. These are replaced by external services - "OCIS_RUN_SERVICES": "app-registry,app-provider,auth-basic,auth-machine,frontend,gateway,graph,groups,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav", - "OCIS_LOG_LEVEL": "info", - "OCIS_URL": OCIS_URL, - "OCIS_BASE_DATA_PATH": "/mnt/data/ocis", - "OCIS_CONFIG_DIR": "/etc/ocis", - "PROXY_ENABLE_BASIC_AUTH": "true", - "FRONTEND_SEARCH_MIN_LENGTH": "2", - "STORAGE_USERS_OCIS_ASYNC_UPLOADS": True, - "OCIS_EVENTS_ENABLE_TLS": False, - } - wait_for_ocis = { - "name": "wait-for-ocis-server", - "image": OC_CI_WAIT_FOR, - "commands": [ - "wait-for -it ocis-server:9200 -t 300", - ], - "depends_on": depends_on, - } - # Pass in "default" accounts_hash_difficulty to not set this environment variable. # That will allow OCIS to use whatever its built-in default is. # Otherwise pass in a value from 4 to about 11 or 12 (default 4, for making regular tests fast) @@ -2214,23 +2092,6 @@ def waitForMiddlewareService(): ], }] -def cloneCoreRepos(): - return [ - { - "name": "clone-core-repos", - "image": OC_CI_ALPINE, - "commands": [ - "source .drone.env", - "git clone -b master --depth=1 https://github.com/owncloud/testing.git %s" % dirs["testing_app"], - "ls -la %s" % dirs["testing_app"], - "git clone -b $CORE_BRANCH --single-branch --no-tags https://github.com/owncloud/core.git %s" % dirs["core"], - "ls -la %s" % dirs["core"], - "cd %s" % dirs["core"], - "git checkout $CORE_COMMITID", - ], - }, - ] - def redis(): return [ { @@ -2607,14 +2468,10 @@ def pipelineSanityChecks(ctx, pipelines): for image in images.keys(): print(" %sx\t%s" % (images[image], image)) -"""Parallel Deployment configs -""" - # configs OCIS_URL = "https://ocis-server:9200" OCIS_DOMAIN = "ocis-server:9200" OC10_URL = "http://oc10:8080" -PARALLEL_DEPLOY_CONFIG_PATH = "%s/tests/parallelDeployAcceptance/drone" % dirs["base"] # step volumes stepVolumeOC10Templates = \ @@ -2670,284 +2527,6 @@ pipeOCISConfigVol = \ "temp": {}, } -def parallelDeployAcceptancePipeline(ctx): - pipelines = [] - - default = { - "filterTags": "~@skip", - } - - for category, params in config["parallelApiTests"].items(): - if "skip" in params and params["skip"]: - return pipelines - - early_fail = params["earlyFail"] if "earlyFail" in params else False - - if type(params["suites"]) == "list": - suites = {} - for suite in params["suites"]: - suites[suite] = suite - else: - suites = params["suites"] - - for suite, suiteName in suites.items(): - params = {} - for item in default: - params[item] = params[item] if item in params else default[item] - - environment = {} - environment["BEHAT_FILTER_TAGS"] = params["filterTags"] - environment["BEHAT_SUITE"] = suite - - pipeline = { - "kind": "pipeline", - "type": "docker", - "name": "parallel-%s" % (suiteName), - "platform": { - "os": "linux", - "arch": "amd64", - }, - "steps": skipIfUnchanged(ctx, "acceptance-tests") + - restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") + - restoreBuildArtifactCache(ctx, "testrunner", dirs["core"]) + - restoreBuildArtifactCache(ctx, "testing_app", dirs["testing_app"]) + - copyConfigs() + - parallelDeploymentOC10Server() + - owncloudLog() + - fixSharedDataPermissions() + - ocisServer( - "ocis", - 4, - [stepVolumeOC10OCISData, stepVolumeOCISConfig], - ["fix-shared-data-permissions"], - "parallel", - ) + - parallelAcceptance(environment) + - failEarly(ctx, early_fail), - "services": oc10DbService() + - ldapService() + - redis(), - "volumes": [ - pipeOC10TemplatesVol, - pipeOC10PreServerVol, - pipeOC10AppsVol, - pipeOC10OCISSharedVol, - pipeOCISConfigVol, - ], - "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]) + - getPipelineNames(cacheCoreReposForTesting(ctx)), - "trigger": {}, - } - - if (ctx.build.event == "cron"): - pipeline["trigger"]["cron"] = params["cron"] if "cron" in params and params["cron"] != "" else "nightly" - else: - pipeline["trigger"]["ref"] = [ - "refs/heads/master", - "refs/pull/**", - ] - - pipelines.append(pipeline) - - return pipelines - -def parallelAcceptance(env): - environment = { - "TEST_SERVER_URL": OCIS_URL, - "TEST_OC10_URL": OC10_URL, - "TEST_PARALLEL_DEPLOYMENT": "true", - "TEST_OCIS": "true", - "TEST_WITH_LDAP": "true", - "REVA_LDAP_PORT": 636, - "REVA_LDAP_BASE_DN": "dc=owncloud,dc=com", - "REVA_LDAP_HOSTNAME": "openldap", - "REVA_LDAP_BIND_DN": "cn=admin,dc=owncloud,dc=com", - "SKELETON_DIR": "/%s/%s/data/apiSkeleton" % (dirs["base"], dirs["testing_app"]), - "PATH_TO_CORE": "/%s/%s" % (dirs["base"], dirs["core"]), - "OCIS_REVA_DATA_ROOT": "/mnt/data/", - "EXPECTED_FAILURES_FILE": "%s/tests/parallelDeployAcceptance/expected-failures-API.md" % dirs["base"], - "OCIS_SKELETON_STRATEGY": "copy", - "SEND_SCENARIO_LINE_REFERENCES": "true", - "UPLOAD_DELETE_WAIT_TIME": "1", - } - environment.update(env) - - return [{ - "name": "acceptance-tests", - "image": OC_CI_PHP % DEFAULT_PHP_VERSION, - "environment": environment, - "commands": [ - "make test-paralleldeployment-api", - ], - "depends_on": ["wait-for-oc10", "wait-for-ocis-server"], - "volumes": [ - stepVolumeOC10Apps, - stepVolumeOC10OCISData, - ], - }] - -def parallelDeploymentOC10Server(): - return [ - { - "name": "oc10", - "image": OC_SERVER, - "detach": True, - "environment": { - # can be switched to "web" - "OWNCLOUD_DEFAULT_APP": "files", - "OWNCLOUD_WEB_REWRITE_LINKS": "false", - # script / config variables - "IDP_OIDC_ISSUER": "https://keycloak/auth/realms/owncloud", - "IDP_OIDC_CLIENT_SECRET": "oc10-oidc-secret", - "CLOUD_DOMAIN": OCIS_DOMAIN, - # LDAP bind configuration - "LDAP_HOST": "openldap", - "LDAP_PORT": 389, - "STORAGE_LDAP_BIND_DN": "cn=admin,dc=owncloud,dc=com", - "STORAGE_LDAP_BIND_PASSWORD": "admin", - # LDAP user configuration - "LDAP_BASE_DN": "dc=owncloud,dc=com", - "LDAP_USER_SCHEMA_DISPLAYNAME": "displayname", - "LDAP_LOGINFILTER": "(&(objectclass=owncloud)(|(uid=%uid)(mail=%uid)))", - "LDAP_GROUP_SCHEMA_DISPLAYNAME": "cn", - "LDAP_USER_SCHEMA_NAME_ATTR": "uid", - "LDAP_GROUP_FILTER": "(&(objectclass=groupOfNames)(objectclass=owncloud))", - "LDAP_USER_SCHEMA_UID": "ownclouduuid", - "LDAP_USERATTRIBUTEFILTERS": "uid", # ownCloudUUID;cn;uid;mail - "LDAP_USER_SCHEMA_MAIL": "mail", - "LDAP_USER_FILTER": "(&(objectclass=owncloud))", - "LDAP_GROUP_MEMBER_ASSOC_ATTR": "uniqueMember", - # database - "OWNCLOUD_DB_TYPE": "mysql", - "OWNCLOUD_DB_NAME": "owncloud", - "OWNCLOUD_DB_USERNAME": "owncloud", - "OWNCLOUD_DB_PASSWORD": "owncloud", - "OWNCLOUD_DB_HOST": "oc10-db", - "OWNCLOUD_ADMIN_USERNAME": "admin", - "OWNCLOUD_ADMIN_PASSWORD": "admin", - "OWNCLOUD_MYSQL_UTF8MB4": "true", - # redis - "OWNCLOUD_REDIS_ENABLED": "true", - "OWNCLOUD_REDIS_HOST": "redis", - # ownCloud config - "OWNCLOUD_TRUSTED_PROXIES": OCIS_DOMAIN, - "OWNCLOUD_OVERWRITE_PROTOCOL": "https", - "OWNCLOUD_OVERWRITE_HOST": OCIS_DOMAIN, - "OWNCLOUD_APPS_ENABLE": "openidconnect,oauth2,user_ldap,graphapi", - "OWNCLOUD_LOG_LEVEL": 2, - "OWNCLOUD_LOG_FILE": "/mnt/data/owncloud.log", - }, - "volumes": [ - stepVolumeOC10OCISData, - stepVolumeOC10Apps, - stepVolumeOC10Templates, - stepVolumeOC10PreServer, - ], - "depends_on": ["copy-configs"], - }, - { - "name": "wait-for-oc10", - "image": OC_CI_WAIT_FOR, - "commands": [ - "wait-for -it oc10:8080 -t 300", - ], - "depends_on": ["oc10"], - }, - ] - -def ldapService(): - return [{ - "name": "openldap", - "image": OSIXIA_OPEN_LDAP, - "environment": { - "LDAP_TLS_VERIFY_CLIENT": "never", - "LDAP_DOMAIN": "owncloud.com", - "LDAP_ORGANISATION": "owncloud", - "LDAP_ADMIN_PASSWORD": "admin", - "LDAP_RFC2307BIS_SCHEMA": "true", - "LDAP_REMOVE_CONFIG_AFTER_SETUP": "false", - "LDAP_SEED_INTERNAL_LDIF_PATH": "%s/ldap/ldif" % (PARALLEL_DEPLOY_CONFIG_PATH), - }, - "command": [ - "--copy-service", - "--loglevel", - "debug", - ], - }] - -def oc10DbService(): - return [ - { - "name": "oc10-db", - "image": MARIADB, - "environment": { - "MYSQL_ROOT_PASSWORD": "owncloud", - "MYSQL_USER": "owncloud", - "MYSQL_PASSWORD": "owncloud", - "MYSQL_DATABASE": "owncloud", - }, - "command": [ - "--max-allowed-packet=128M", - "--innodb-log-file-size=64M", - "--innodb-read-only-compressed=OFF", - ], - }, - ] - -def copyConfigs(): - return [{ - "name": "copy-configs", - "image": OC_SERVER, - "commands": [ - # ocis proxy config - "mkdir -p /etc/ocis", - "cp %s/ocis/proxy.yaml /etc/ocis/proxy.yaml" % (PARALLEL_DEPLOY_CONFIG_PATH), - "chown -R 33:33 /etc/ocis", - # oc10 configs - "mkdir -p /etc/templates", - "mkdir -p /etc/pre_server.d", - "cp %s/oc10/oidc.config.php /etc/templates/oidc.config.php" % (PARALLEL_DEPLOY_CONFIG_PATH), - "cp %s/oc10/ldap-config.tmpl.json /etc/templates/ldap-config.tmpl.json" % (PARALLEL_DEPLOY_CONFIG_PATH), - "cp %s/oc10/10-custom-config.sh /etc/pre_server.d/10-custom-config.sh" % (PARALLEL_DEPLOY_CONFIG_PATH), - ], - "volumes": [ - stepVolumeOCISConfig, - stepVolumeOC10Templates, - stepVolumeOC10PreServer, - ], - }] - -def owncloudLog(): - return [{ - "name": "owncloud-log", - "image": OC_UBUNTU, - "detach": True, - "commands": [ - "tail -f /mnt/data/owncloud.log", - ], - "volumes": [ - stepVolumeOC10OCISData, - ], - "depends_on": ["wait-for-oc10"], - }] - -def fixSharedDataPermissions(): - return [{ - "name": "fix-shared-data-permissions", - "image": OC_CI_PHP % DEFAULT_PHP_VERSION, - "commands": [ - "chown -R 33:33 /var/www/owncloud", # www-data user - "chmod -R 777 /var/www/owncloud", - "chown -R 33:33 /mnt/data", # www-data user - "chmod -R 777 /mnt/data", - ], - "volumes": [ - stepVolumeOC10Apps, - stepVolumeOC10OCISData, - ], - "depends_on": ["wait-for-oc10"], - }] - def litmus(ctx, storage): pipelines = [] diff --git a/Makefile b/Makefile index 51c56eb0659..d9e993d8ea1 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,7 @@ help: @echo -e "${GREEN}Testing with test suite natively installed:${RESET}\n" @echo -e "${PURPLE}\tdocs: https://owncloud.dev/ocis/development/testing/#testing-with-test-suite-natively-installed${RESET}\n" @echo -e "\tmake test-acceptance-api\t\t${BLUE}run API acceptance tests${RESET}" + @echo -e "\tmake test-acceptance-core-api\t\t${BLUE}run core API acceptance tests${RESET}" @echo -e "\tmake test-paralleldeployment-api\t${BLUE}run API acceptance tests for parallel deployment${RESET}" @echo -e "\tmake clean-tests\t\t\t${BLUE}delete API tests framework dependencies${RESET}" @echo @@ -103,14 +104,20 @@ clean-tests: BEHAT_BIN=vendor-bin/behat/vendor/bin/behat # behat config file for parallel deployment tests PARALLEL_BEHAT_YML=tests/parallelDeployAcceptance/config/behat.yml +# behat config file for core api tests +CORE_BEHAT_YML=tests/acceptance/config/behat-core.yml .PHONY: test-acceptance-api test-acceptance-api: vendor-bin/behat/vendor - BEHAT_BIN=$(BEHAT_BIN) $(PATH_TO_CORE)/tests/acceptance/run.sh --remote --type api + BEHAT_BIN=$(BEHAT_BIN) $(PWD)/tests/acceptance/run.sh --type api + +.PHONY: test-acceptance-core-api +test-acceptance-core-api: vendor-bin/behat/vendor + BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(CORE_BEHAT_YML) $(PWD)/tests/acceptance/run.sh --type core-api .PHONY: test-paralleldeployment-api test-paralleldeployment-api: vendor-bin/behat/vendor - BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(PARALLEL_BEHAT_YML) $(PATH_TO_CORE)/tests/acceptance/run.sh --type api + BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(PARALLEL_BEHAT_YML) $(PWD)/tests/acceptance/run.sh --type api vendor/bamarni/composer-bin-plugin: composer.lock composer install diff --git a/composer.json b/composer.json index dddfbd2bc48..ddff8e34717 100644 --- a/composer.json +++ b/composer.json @@ -9,9 +9,8 @@ "bamarni/composer-bin-plugin": true } }, - "require": { - }, "require-dev": { + "ext-simplexml": "*", "bamarni/composer-bin-plugin": "^1.4" }, "extra": { diff --git a/tests/TestHelpers/Asserts/WebDav.php b/tests/TestHelpers/Asserts/WebDav.php new file mode 100644 index 00000000000..2ca6e14d8d9 --- /dev/null +++ b/tests/TestHelpers/Asserts/WebDav.php @@ -0,0 +1,200 @@ + + * @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers\Asserts; + +use Exception; +use SimpleXMLElement; +use TestHelpers\DownloadHelper; +use TestHelpers\SetupHelper; + +/** + * WebDAV related asserts + */ +class WebDav extends \PHPUnit\Framework\Assert { + /** + * + * @param string|null $element exception|message|reason + * @param string|null $expectedValue + * @param array|null $responseXml + * @param string|null $extraErrorText + * + * @return void + */ + public static function assertDavResponseElementIs( + ?string $element, + ?string $expectedValue, + ?array $responseXml, + ?string $extraErrorText = '' + ):void { + if ($extraErrorText !== '') { + $extraErrorText = $extraErrorText . " "; + } + self::assertArrayHasKey( + 'value', + $responseXml, + $extraErrorText . "responseXml does not have key 'value'" + ); + if ($element === "exception") { + $result = $responseXml['value'][0]['value']; + } elseif ($element === "message") { + $result = $responseXml['value'][1]['value']; + } elseif ($element === "reason") { + $result = $responseXml['value'][3]['value']; + } + + self::assertEquals( + $expectedValue, + $result, + __METHOD__ . " " . $extraErrorText . "Expected '$expectedValue' in element $element got '$result'" + ); + } + + /** + * + * @param SimpleXMLElement $responseXmlObject + * @param array|null $expectedShareTypes + * + * @return void + */ + public static function assertResponseContainsShareTypes( + SimpleXMLElement $responseXmlObject, + ?array $expectedShareTypes + ):void { + foreach ($expectedShareTypes as $row) { + $xmlPart = $responseXmlObject->xpath( + "//d:prop/oc:share-types/oc:share-type[.=" . $row[0] . "]" + ); + self::assertNotEmpty( + $xmlPart, + "cannot find share-type '" . $row[0] . "'" + ); + } + } + + /** + * Asserts that the content of a remote and a local file is the same + * or is different + * + * @param string|null $baseUrl + * @param string|null $username + * @param string|null $password + * @param string|null $remoteFile + * @param string|null $localFile + * @param string|null $xRequestId + * @param bool $shouldBeSame (default true) if true then check that the file contents are the same + * otherwise check that the file contents are different + * + * @return void + */ + public static function assertContentOfRemoteAndLocalFileIsSame( + ?string $baseUrl, + ?string $username, + ?string $password, + ?string $remoteFile, + ?string $localFile, + ?string $xRequestId = '', + ?bool $shouldBeSame = true + ):void { + $result = DownloadHelper::download( + $baseUrl, + $username, + $password, + $remoteFile, + $xRequestId + ); + + $localContent = \file_get_contents($localFile); + $downloadedContent = $result->getBody()->getContents(); + + if ($shouldBeSame) { + self::assertSame( + $localContent, + $downloadedContent + ); + } else { + self::assertNotSame( + $localContent, + $downloadedContent + ); + } + } + + /** + * Asserts that the content of a remote file (downloaded by DAV) + * and a file in the skeleton folder of the system under test is the same + * or is different + * + * @param string|null $baseUrl + * @param string|null $username + * @param string|null $password + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $remoteFile + * @param string|null $fileInSkeletonFolder + * @param string|null $xRequestId + * @param bool $shouldBeSame (default true) if true then check that the file contents are the same + * otherwise check that the file contents are different + * + * @return void + * @throws Exception + */ + public static function assertContentOfDAVFileAndSkeletonFileOnSUT( + ?string $baseUrl, + ?string $username, + ?string $password, + ?string $adminUsername, + ?string $adminPassword, + ?string $remoteFile, + ?string $fileInSkeletonFolder, + ?string $xRequestId = '', + ?bool $shouldBeSame = true + ):void { + $result = DownloadHelper::download( + $baseUrl, + $username, + $password, + $remoteFile, + $xRequestId + ); + $downloadedContent = $result->getBody()->getContents(); + + $localContent = SetupHelper::readSkeletonFile( + $fileInSkeletonFolder, + $xRequestId, + $baseUrl, + $adminUsername, + $adminPassword + ); + + if ($shouldBeSame) { + self::assertSame( + $localContent, + $downloadedContent + ); + } else { + self::assertNotSame( + $localContent, + $downloadedContent + ); + } + } +} diff --git a/tests/TestHelpers/HttpRequestHelper.php b/tests/TestHelpers/HttpRequestHelper.php new file mode 100644 index 00000000000..c6b567b7a43 --- /dev/null +++ b/tests/TestHelpers/HttpRequestHelper.php @@ -0,0 +1,622 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +namespace TestHelpers; + +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Cookie\CookieJar; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use SimpleXMLElement; +use Sabre\Xml\LibXMLException; +use Sabre\Xml\Reader; +use GuzzleHttp\Pool; + +/** + * Helper for HTTP requests + */ +class HttpRequestHelper { + public const HTTP_TOO_EARLY = 425; + + /** + * @var string + */ + private static $oCSelectorCookie = null; + + /** + * @return string + */ + public static function getOCSelectorCookie(): string { + return self::$oCSelectorCookie; + } + + /** + * @param string $oCSelectorCookie "owncloud-selector=oc10;path=/;" + * + * @return void + */ + public static function setOCSelectorCookie(string $oCSelectorCookie): void { + self::$oCSelectorCookie = $oCSelectorCookie; + } + + /** + * Some systems-under-test do async post-processing of operations like upload, + * move etc. If a client does a request on the resource before the post-processing + * is finished, then the server should return HTTP_TOO_EARLY "425". Clients are + * expected to retry the request "some time later" (tm). + * + * On such systems, when HTTP_TOO_EARLY status is received, the test code will + * retry the request at 1-second intervals until either some other HTTP status + * is received or the retry-limit is reached. + * + * @return int + */ + public static function numRetriesOnHttpTooEarly():int { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently reva and oCIS may return HTTP_TOO_EARLY + // So try up to 10 times before giving up. + return 10; + } + return 0; + } + + /** + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $method + * @param string|null $user + * @param string|null $password + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param mixed $body + * @param array|null $config + * @param CookieJar|null $cookies + * @param bool $stream Set to true to stream a response rather + * than download it all up-front. + * @param int|null $timeout + * @param Client|null $client + * + * @return ResponseInterface + * @throws GuzzleException + */ + public static function sendRequest( + ?string $url, + ?string $xRequestId, + ?string $method = 'GET', + ?string $user = null, + ?string $password = null, + ?array $headers = null, + $body = null, + ?array $config = null, + ?CookieJar $cookies = null, + bool $stream = false, + ?int $timeout = 0, + ?Client $client = null + ):ResponseInterface { + if ($client === null) { + $client = self::createClient( + $user, + $password, + $config, + $cookies, + $stream, + $timeout + ); + } + /** + * @var RequestInterface $request + */ + $request = self::createRequest( + $url, + $xRequestId, + $method, + $headers, + $body + ); + + if ((\getenv('DEBUG_ACCEPTANCE_REQUESTS') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) { + $debugRequests = true; + } else { + $debugRequests = false; + } + + if ((\getenv('DEBUG_ACCEPTANCE_RESPONSES') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) { + $debugResponses = true; + } else { + $debugResponses = false; + } + + if ($debugRequests) { + self::debugRequest($request, $user, $password); + } + + $sendRetryLimit = self::numRetriesOnHttpTooEarly(); + $sendCount = 0; + $sendExceptionHappened = false; + + do { + // The exceptions that might happen here include: + // ConnectException - in that case there is no response. Don't catch the exception. + // RequestException - if there is something in the response then pass it back. + // otherwise re-throw the exception. + // GuzzleException - something else unexpected happened. Don't catch the exception. + try { + $response = $client->send($request); + } catch (RequestException $ex) { + $sendExceptionHappened = true; + $response = $ex->getResponse(); + + //if the response was null for some reason do not return it but re-throw + if ($response === null) { + throw $ex; + } + } + + if ($debugResponses) { + self::debugResponse($response); + } + $sendCount = $sendCount + 1; + $loopAgain = !$sendExceptionHappened && ($response->getStatusCode() === self::HTTP_TOO_EARLY) && ($sendCount <= $sendRetryLimit); + if ($loopAgain) { + // we need to repeat the send request, because we got HTTP_TOO_EARLY + // wait 1 second before sending again, to give the server some time + // to finish whatever post-processing it might be doing. + \sleep(1); + } + } while ($loopAgain); + + return $response; + } + + /** + * Print details about the request. + * + * @param RequestInterface|null $request + * @param string|null $user + * @param string|null $password + * + * @return void + */ + private static function debugRequest(?RequestInterface $request, ?string $user, ?string $password):void { + print("### AUTH: $user:$password\n"); + print("### REQUEST: " . $request->getMethod() . " " . $request->getUri() . "\n"); + self::printHeaders($request->getHeaders()); + self::printBody($request->getBody()); + print("\n### END REQUEST\n"); + } + + /** + * Print details about the response. + * + * @param ResponseInterface|null $response + * + * @return void + */ + private static function debugResponse(?ResponseInterface $response):void { + print("### RESPONSE\n"); + print("Status: " . $response->getStatusCode() . "\n"); + self::printHeaders($response->getHeaders()); + self::printBody($response->getBody()); + print("\n### END RESPONSE\n"); + } + + /** + * Print details about the headers. + * + * @param array|null $headers + * + * @return void + */ + private static function printHeaders(?array $headers):void { + if ($headers) { + print("Headers:\n"); + foreach ($headers as $header => $value) { + if (\is_array($value)) { + print($header . ": " . \implode(', ', $value) . "\n"); + } else { + print($header . ": " . $value . "\n"); + } + } + } else { + print("Headers: none\n"); + } + } + + /** + * Print details about the body. + * + * @param StreamInterface|null $body + * + * @return void + */ + private static function printBody(?StreamInterface $body):void { + print("Body:\n"); + \var_dump($body->getContents()); + // Rewind the stream so that later code can read from the start. + $body->rewind(); + } + + /** + * Send the requests to the server in parallel. + * This function takes an array of requests and an optional client. + * It will send all the requests to the server using the Pool object in guzzle. + * + * @param array|null $requests + * @param Client|null $client + * + * @return array + */ + public static function sendBatchRequest( + ?array $requests, + ?Client $client + ):array { + $results = Pool::batch($client, $requests); + return $results; + } + + /** + * Create a Guzzle Client + * This creates a client object that can be used later to send a request object(s) + * + * @param string|null $user + * @param string|null $password + * @param array|null $config + * @param CookieJar|null $cookies + * @param bool $stream Set to true to stream a response rather + * than download it all up-front. + * @param int|null $timeout + * + * @return Client + */ + public static function createClient( + ?string $user = null, + ?string $password = null, + ?array $config = null, + ?CookieJar $cookies = null, + ?bool $stream = false, + ?int $timeout = 0 + ):Client { + $options = []; + if ($user !== null) { + $options['auth'] = [$user, $password]; + } + if ($config !== null) { + $options['config'] = $config; + } + if ($cookies !== null) { + $options['cookies'] = $cookies; + } + $options['stream'] = $stream; + $options['verify'] = false; + $options['timeout'] = $timeout; + $client = new Client($options); + return $client; + } + + /** + * Create an http request based on given parameters. + * This creates a RequestInterface object that can be used with a client to send a request. + * This enables us to create multiple requests in advance so that we can send them to the server at once in parallel. + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $method + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param string|array $body either the actual string to send in the body, + * or an array of key-value pairs to be converted + * into a body with http_build_query. + * + * @return RequestInterface + */ + public static function createRequest( + ?string $url, + ?string $xRequestId = '', + ?string $method = 'GET', + ?array $headers = null, + $body = null + ):RequestInterface { + if ($headers === null) { + $headers = []; + } + if ($xRequestId !== '') { + $headers['X-Request-ID'] = $xRequestId; + } + if (\is_array($body)) { + // when creating the client, it is possible to set 'form_params' and + // the Client constructor sorts out doing this http_build_query stuff. + // But 'new Request' does not have the flexibility to do that. + // So we need to do it here. + $body = \http_build_query($body, '', '&'); + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (OcisHelper::isTestingParallelDeployment()) { + // oCIS cannot handle '/apps/testing' endpoints + // so those requests must be redirected to oC10 server + // change server to oC10 if the request url has `/apps/testing` + if (strpos($url, "/apps/testing") !== false) { + $oCISServerUrl = \getenv('TEST_SERVER_URL'); + $oC10ServerUrl = \getenv('TEST_OC10_URL'); + $url = str_replace($oCISServerUrl, $oC10ServerUrl, $url); + } else { + // set 'owncloud-server' selector cookie for oCIS requests + $headers['Cookie'] = self::getOCSelectorCookie(); + } + } + + $request = new Request( + $method, + $url, + $headers, + $body + ); + return $request; + } + + /** + * same as HttpRequestHelper::sendRequest() but with "GET" as method + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $user + * @param string|null $password + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param mixed $body + * @param array|null $config + * @param CookieJar|null $cookies + * @param boolean $stream + * + * @return ResponseInterface + * @throws GuzzleException + * @see HttpRequestHelper::sendRequest() + */ + public static function get( + ?string $url, + ?string $xRequestId, + ?string $user = null, + ?string $password = null, + ?array $headers = null, + $body = null, + ?array $config = null, + ?CookieJar $cookies = null, + ?bool $stream = false + ):ResponseInterface { + return self::sendRequest( + $url, + $xRequestId, + 'GET', + $user, + $password, + $headers, + $body, + $config, + $cookies, + $stream + ); + } + + /** + * same as HttpRequestHelper::sendRequest() but with "POST" as method + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $user + * @param string|null $password + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param mixed $body + * @param array|null $config + * @param CookieJar|null $cookies + * @param boolean $stream + * + * @return ResponseInterface + * @throws GuzzleException + * @see HttpRequestHelper::sendRequest() + */ + public static function post( + ?string $url, + ?string $xRequestId, + ?string $user = null, + ?string $password = null, + ?array $headers = null, + $body = null, + ?array $config = null, + ?CookieJar $cookies = null, + ?bool $stream = false + ):ResponseInterface { + return self::sendRequest( + $url, + $xRequestId, + 'POST', + $user, + $password, + $headers, + $body, + $config, + $cookies, + $stream + ); + } + + /** + * same as HttpRequestHelper::sendRequest() but with "PUT" as method + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $user + * @param string|null $password + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param mixed $body + * @param array|null $config + * @param CookieJar|null $cookies + * @param boolean $stream + * + * @return ResponseInterface + * @throws GuzzleException + * @see HttpRequestHelper::sendRequest() + */ + public static function put( + ?string $url, + ?string $xRequestId, + ?string $user = null, + ?string $password = null, + ?array $headers = null, + $body = null, + ?array $config = null, + ?CookieJar $cookies = null, + ?bool $stream = false + ):ResponseInterface { + return self::sendRequest( + $url, + $xRequestId, + 'PUT', + $user, + $password, + $headers, + $body, + $config, + $cookies, + $stream + ); + } + + /** + * same as HttpRequestHelper::sendRequest() but with "DELETE" as method + * + * @param string|null $url + * @param string|null $xRequestId + * @param string|null $user + * @param string|null $password + * @param array|null $headers ['X-MyHeader' => 'value'] + * @param mixed $body + * @param array|null $config + * @param CookieJar|null $cookies + * @param boolean $stream + * + * @return ResponseInterface + * @throws GuzzleException + * @see HttpRequestHelper::sendRequest() + * + */ + public static function delete( + ?string $url, + ?string $xRequestId, + ?string $user = null, + ?string $password = null, + ?array $headers = null, + $body = null, + ?array $config = null, + ?CookieJar $cookies = null, + ?bool $stream = false + ):ResponseInterface { + return self::sendRequest( + $url, + $xRequestId, + 'DELETE', + $user, + $password, + $headers, + $body, + $config, + $cookies, + $stream + ); + } + + /** + * Parses the response as XML and returns a SimpleXMLElement with these + * registered namespaces: + * | prefix | namespace | + * | d | DAV: | + * | oc | http://owncloud.org/ns | + * | ocs | http://open-collaboration-services.org/ns | + * + * @param ResponseInterface $response + * @param string|null $exceptionText text to put at the front of exception messages + * + * @return SimpleXMLElement + * @throws Exception + */ + public static function getResponseXml(ResponseInterface $response, ?string $exceptionText = ''):SimpleXMLElement { + // rewind just to make sure we can re-parse it in case it was parsed already... + $response->getBody()->rewind(); + $contents = $response->getBody()->getContents(); + try { + $responseXmlObject = new SimpleXMLElement($contents); + $responseXmlObject->registerXPathNamespace( + 'ocs', + 'http://open-collaboration-services.org/ns' + ); + $responseXmlObject->registerXPathNamespace( + 'oc', + 'http://owncloud.org/ns' + ); + $responseXmlObject->registerXPathNamespace( + 'd', + 'DAV:' + ); + return $responseXmlObject; + } catch (Exception $e) { + if ($exceptionText !== '') { + $exceptionText = $exceptionText . ' '; + } + if ($contents === '') { + throw new Exception($exceptionText . "Received empty response where XML was expected"); + } + $message = $exceptionText . "Exception parsing response body: \"" . $contents . "\""; + throw new Exception($message, 0, $e); + } + } + + /** + * parses the body content of $response and returns an array representing the XML + * This function returns an array with the following three elements: + * * name - The root element name. + * * value - The value for the root element. + * * attributes - An array of attributes. + * + * @param ResponseInterface $response + * + * @return array + */ + public static function parseResponseAsXml(ResponseInterface $response):array { + $body = $response->getBody()->getContents(); + $parsedResponse = []; + if ($body && \substr($body, 0, 1) === '<') { + try { + $reader = new Reader(); + $reader->xml($body); + $parsedResponse = $reader->parse(); + } catch (LibXMLException $e) { + // Sometimes the body can be a real page of HTML and text. + // So it may not be a complete ordinary piece of XML. + // The XML parse might fail with an exception message like: + // Opening and ending tag mismatch: link line 31 and head. + } + } + return $parsedResponse; + } +} diff --git a/tests/TestHelpers/LoggingHelper.php b/tests/TestHelpers/LoggingHelper.php new file mode 100644 index 00000000000..574527ef8ca --- /dev/null +++ b/tests/TestHelpers/LoggingHelper.php @@ -0,0 +1,457 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Exception; +use InvalidArgumentException; + +/** + * Helper to read and analyze the owncloud log file + * + * @author Artur Neumann + * + */ +class LoggingHelper { + /** + * @var array + */ + public const LOG_LEVEL_ARRAY = [ + "debug", + "info", + "warning", + "error", + "fatal" + ]; + + /** + * returns the log file path local to the system ownCloud is running on + * + * @param string|null $xRequestId + * + * @return string + * @throws Exception + */ + public static function getLogFilePath( + ?string $xRequestId = '' + ):string { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + return ""; + } + $result = SetupHelper::runOcc( + ['log:owncloud'], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not get owncloud log file information" . + $result ["stdOut"] . " " . $result ["stdErr"] + ); + } + \preg_match( + "/Log backend ownCloud: (\w+)\sLog file: (.*)/", + $result ['stdOut'], + $matches + ); + if (!isset($matches[1]) || $matches[1] !== "enabled") { + throw new Exception("log backend is not set to 'owncloud'"); + } + if (!isset($matches[2])) { + throw new Exception("could not get owncloud log file information"); + } + return $matches[2]; + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $noOfLinesToRead + * + * @return array + * @throws Exception + */ + public static function getLogFileContent( + ?string $baseUrl, + ?string $adminUsername, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $noOfLinesToRead = 0 + ):array { + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "GET", + "/apps/testing/api/v1/logfile/$noOfLinesToRead", + $xRequestId + ); + if ($result->getStatusCode() !== 200) { + throw new Exception( + "could not get logfile content " . $result->getReasonPhrase() + ); + } + $response = HttpRequestHelper::getResponseXml($result, __METHOD__); + + $result = []; + foreach ($response->data->element as $line) { + array_push($result, (string)$line); + } + return $result; + } + + /** + * returns the currently set log level [debug, info, warning, error, fatal] + * + * @param string|null $xRequestId + * + * @return string + * @throws Exception + */ + public static function getLogLevel( + ?string $xRequestId = '' + ):string { + if (OcisHelper::isTestingOnOcisOrReva()) { + return "debug"; + } + $result = SetupHelper::runOcc( + ["log:manage"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not get log level " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + if (!\preg_match("/Log level:\s(\w+)\s\(/", $result["stdOut"], $matches)) { + throw new Exception("could not get log level"); + } + return \strtolower($matches[1]); + } + + /** + * + * @param string|null $logLevel (debug|info|warning|error|fatal) + * @param string|null $xRequestId + * + * @return void + * @throws Exception + */ + public static function setLogLevel( + ?string $logLevel, + ?string $xRequestId = '' + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we can't manage log file settings on reva or OCIS + return; + } + if (!\in_array($logLevel, self::LOG_LEVEL_ARRAY)) { + throw new InvalidArgumentException("invalid log level"); + } + $result = SetupHelper::runOcc( + ["log:manage", "--level=$logLevel"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not set log level " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + } + + /** + * returns the currently set logging backend (owncloud|syslog|errorlog) + * + * @param string|null $xRequestId + * + * @return string + * @throws Exception + */ + public static function getLogBackend( + ?string $xRequestId = '' + ):string { + if (OcisHelper::isTestingOnOcisOrReva()) { + return "errorlog"; + } + $result = SetupHelper::runOcc( + ["log:manage"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not get log backend " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + $pregResult = \preg_match( + "/Enabled logging backend:\s(\w+)\n/", + $result ["stdOut"], + $matches + ); + if (!$pregResult) { + throw new Exception("could not get log backend"); + } + return \strtolower($matches[1]); + } + + /** + * + * @param string|null $backend (owncloud|syslog|errorlog) + * @param string|null $xRequestId + * + * @return void + * @throws Exception + */ + public static function setLogBackend( + ?string $backend, + ?string $xRequestId = '' + ):void { + if (!\in_array($backend, ["owncloud", "syslog", "errorlog"])) { + throw new InvalidArgumentException("invalid log backend"); + } + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we can't manage log file settings on reva or OCIS + return; + } + $result = SetupHelper::runOcc( + ["log:manage", "--backend=$backend"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not set log backend " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + } + + /** + * returns the currently set logging timezone + * + * @param string|null $xRequestId + * + * @return string + * @throws Exception + */ + public static function getLogTimezone( + ?string $xRequestId = '' + ):string { + if (OcisHelper::isTestingOnOcisOrReva()) { + return "UTC"; + } + $result = SetupHelper::runOcc( + ["log:manage"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not get log timezone " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + $pregResult = \preg_match( + "/Log timezone:\s(\w+)/", + $result ["stdOut"], + $matches + ); + if (!$pregResult) { + throw new Exception("could not get log timezone"); + } + return $matches[1]; + } + + /** + * + * @param string|null $timezone + * @param string|null $xRequestId + * + * @return void + * @throws Exception + */ + public static function setLogTimezone( + ?string $timezone, + ?string $xRequestId = '' + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we can't manage log file settings on reva or OCIS + return; + } + $result = SetupHelper::runOcc( + ["log:manage", "--timezone=$timezone"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not set log timezone " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $xRequestId + * + * @return void + * @throws Exception + */ + public static function clearLogFile( + ?string $baseUrl, + ?string $adminUsername, + ?string $adminPassword, + ?string $xRequestId = '' + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + return; + } + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "DELETE", + "/apps/testing/api/v1/logfile", + $xRequestId + ); + if ($result->getStatusCode() !== 200) { + throw new Exception("could not clear logfile"); + } + } + + /** + * + * @param string|null $logLevel + * @param string|null $backend + * @param string|null $timezone + * @param string|null $xRequestId + * + * @return void + * @throws Exception + */ + public static function restoreLoggingStatus( + ?string $logLevel, + ?string $backend, + ?string $timezone, + ?string $xRequestId = '' + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + return; + } + if (!\in_array(\strtolower($logLevel), self::LOG_LEVEL_ARRAY)) { + throw new InvalidArgumentException("invalid log level"); + } + if (!\in_array(\strtolower($backend), ["owncloud", "syslog", "errorlog"])) { + throw new InvalidArgumentException("invalid log backend"); + } + + $commands = ["log:manage"]; + + if ($timezone) { + \array_push($commands, "--timezone=$timezone"); + } + if ($logLevel) { + \array_push($commands, "--backend=$backend"); + } + if ($backend) { + \array_push($commands, "--level=$logLevel"); + } + + if (\count($commands) > 1) { + $result = SetupHelper::runOcc( + $commands, + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not restore log status " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + } + } + + /** + * returns the currently set log level, backend and timezone + * + * @param string|null $xRequestId + * + * @return array|string[] + * @throws Exception + */ + public static function getLogInfo( + ?string $xRequestId = '' + ):array { + if (OcisHelper::isTestingOnOcisOrReva()) { + return [ + "level" => "debug", + "backend" => "errorlog", + "timezone" => "UTC" + ]; + } + $result = SetupHelper::runOcc( + ["log:manage"], + $xRequestId + ); + if ($result["code"] != 0) { + throw new Exception( + "could not get log level " . $result ["stdOut"] . " " . + $result ["stdErr"] + ); + } + + $logging = []; + if (!\preg_match("/Log level:\s(\w+)\s\(/", $result["stdOut"], $matches)) { + throw new Exception("could not get log level"); + } + $logging["level"] = $matches[1]; + + $pregResult = \preg_match( + "/Log timezone:\s(\w+)/", + $result ["stdOut"], + $matches + ); + if (!$pregResult) { + throw new Exception("could not get log timezone"); + } + $logging["timezone"] = $matches[1]; + + $pregResult = \preg_match( + "/Enabled logging backend:\s(\w+)\n/", + $result ["stdOut"], + $matches + ); + if (!$pregResult) { + throw new Exception("could not get log backend"); + } + $logging["backend"] = $matches[1]; + + return $logging; + } +} diff --git a/tests/TestHelpers/OcisHelper.php b/tests/TestHelpers/OcisHelper.php new file mode 100644 index 00000000000..e8627babaef --- /dev/null +++ b/tests/TestHelpers/OcisHelper.php @@ -0,0 +1,364 @@ + + * @copyright Copyright (c) 2020 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +namespace TestHelpers; + +use Exception; +use GuzzleHttp\Exception\GuzzleException; + +/** + * Class OcisHelper + * + * Helper functions that are needed to run tests on OCIS + * + * @package TestHelpers + */ +class OcisHelper { + /** + * @return bool + */ + public static function isTestingOnOcis():bool { + return (\getenv("TEST_OCIS") === "true"); + } + + /** + * @return bool + */ + public static function isTestingOnReva():bool { + return (\getenv("TEST_REVA") === "true"); + } + + /** + * @return bool + */ + public static function isTestingOnOcisOrReva():bool { + return (self::isTestingOnOcis() || self::isTestingOnReva()); + } + + /** + * @return bool + */ + public static function isTestingOnOc10():bool { + return (!self::isTestingOnOcisOrReva()); + } + + /** + * @return bool + */ + public static function isTestingParallelDeployment(): bool { + return (\getenv("TEST_PARALLEL_DEPLOYMENT") === "true"); + } + + /** + * @return bool + */ + public static function isTestingWithGraphApi(): bool { + return \getenv('TEST_WITH_GRAPH_API') === 'true'; + } + + /** + * @return bool|string false if no command given or the command as string + */ + public static function getDeleteUserDataCommand() { + $cmd = \getenv("DELETE_USER_DATA_CMD"); + if ($cmd === false || \trim($cmd) === "") { + return false; + } + return $cmd; + } + + /** + * @return string + * @throws Exception + */ + public static function getStorageDriver():string { + $storageDriver = (\getenv("STORAGE_DRIVER")); + if ($storageDriver === false) { + return "OWNCLOUD"; + } + $storageDriver = \strtoupper($storageDriver); + if ($storageDriver !== "OCIS" && $storageDriver !== "EOS" && $storageDriver !== "OWNCLOUD" && $storageDriver !== "S3NG") { + throw new Exception( + "Invalid storage driver. " . + "STORAGE_DRIVER must be OCIS|EOS|OWNCLOUD|S3NG" + ); + } + return $storageDriver; + } + + /** + * @param string|null $user + * + * @return void + * @throws Exception + */ + public static function deleteRevaUserData(?string $user = ""):void { + $deleteCmd = self::getDeleteUserDataCommand(); + if ($deleteCmd === false) { + if (self::getStorageDriver() === "OWNCLOUD") { + self::recurseRmdir(self::getOcisRevaDataRoot() . $user); + } + return; + } + if (self::getStorageDriver() === "EOS") { + $deleteCmd = \str_replace( + "%s", + $user[0] . '/' . $user, + $deleteCmd + ); + } else { + $deleteCmd = \sprintf($deleteCmd, $user); + } + \exec($deleteCmd); + } + + /** + * Helper for Recursive Copy of file/folder + * For more info check this out https://gist.github.com/gserrano/4c9648ec9eb293b9377b + * + * @param string|null $source + * @param string|null $destination + * + * @return void + */ + public static function recurseCopy(?string $source, ?string $destination):void { + $dir = \opendir($source); + @\mkdir($destination); + while (($file = \readdir($dir)) !== false) { + if (($file != '.') && ($file != '..')) { + if (\is_dir($source . '/' . $file)) { + self::recurseCopy($source . '/' . $file, $destination . '/' . $file); + } else { + \copy($source . '/' . $file, $destination . '/' . $file); + } + } + } + \closedir($dir); + } + + /** + * Helper for Recursive Upload of file/folder + * + * @param string|null $baseUrl + * @param string|null $source + * @param string|null $userId + * @param string|null $password + * @param string|null $xRequestId + * @param string|null $destination + * + * @return void + * @throws Exception + */ + public static function recurseUpload( + ?string $baseUrl, + ?string $source, + ?string $userId, + ?string $password, + ?string $xRequestId = '', + ?string $destination = '' + ):void { + if ($destination !== '') { + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $userId, + $password, + "MKCOL", + $destination, + [], + $xRequestId + ); + if ($response->getStatusCode() !== 201) { + throw new Exception("Could not create folder destination" . $response->getBody()->getContents()); + } + } + + $dir = \opendir($source); + while (($file = \readdir($dir)) !== false) { + if (($file != '.') && ($file != '..')) { + $sourcePath = $source . '/' . $file; + $destinationPath = $destination . '/' . $file; + if (\is_dir($sourcePath)) { + self::recurseUpload( + $baseUrl, + $sourcePath, + $userId, + $password, + $xRequestId, + $destinationPath + ); + } else { + $response = UploadHelper::upload( + $baseUrl, + $userId, + $password, + $sourcePath, + $destinationPath, + $xRequestId + ); + $responseStatus = $response->getStatusCode(); + if ($responseStatus !== 201) { + throw new Exception( + "Could not upload skeleton file $sourcePath to $destinationPath for user '$userId' status '$responseStatus' response body: '" + . $response->getBody()->getContents() . "'" + ); + } + } + } + } + \closedir($dir); + } + + /** + * @return int + */ + public static function getLdapPort():int { + $port = \getenv("REVA_LDAP_PORT"); + return $port ? (int)$port : 636; + } + + /** + * @return bool + */ + public static function useSsl():bool { + $useSsl = \getenv("REVA_LDAP_USESSL"); + if ($useSsl === false) { + return (self::getLdapPort() === 636); + } else { + return $useSsl === "true"; + } + } + + /** + * @return string + */ + public static function getBaseDN():string { + $dn = \getenv("REVA_LDAP_BASE_DN"); + return $dn ? $dn : "dc=owncloud,dc=com"; + } + + /** + * @return string + */ + public static function getGroupsOU():string { + $ou = \getenv("REVA_LDAP_GROUPS_OU"); + return $ou ? $ou : "TestGroups"; + } + + /** + * @return string + */ + public static function getUsersOU():string { + $ou = \getenv("REVA_LDAP_USERS_OU"); + return $ou ? $ou : "TestUsers"; + } + + /** + * @return string + */ + public static function getGroupSchema():string { + $schema = \getenv("REVA_LDAP_GROUP_SCHEMA"); + return $schema ? $schema : "rfc2307"; + } + /** + * @return string + */ + public static function getHostname():string { + $hostname = \getenv("REVA_LDAP_HOSTNAME"); + return $hostname ? $hostname : "localhost"; + } + + /** + * @return string + */ + public static function getBindDN():string { + $dn = \getenv("REVA_LDAP_BIND_DN"); + return $dn ? $dn : "cn=admin,dc=owncloud,dc=com"; + } + + /** + * @return string + */ + public static function getBindPassword():string { + $pw = \getenv("REVA_LDAP_BIND_PASSWORD"); + return $pw ? $pw : ""; + } + + /** + * @return string + */ + private static function getOcisRevaDataRoot():string { + $root = \getenv("OCIS_REVA_DATA_ROOT"); + if (($root === false || $root === "") && self::isTestingOnOcisOrReva()) { + $root = "/var/tmp/ocis/owncloud/"; + } + if (!\file_exists($root)) { + echo "WARNING: reva data root folder ($root) does not exist\n"; + } + return $root; + } + + /** + * @param string|null $dir + * + * @return bool + */ + private static function recurseRmdir(?string $dir):bool { + if (\file_exists($dir) === true) { + $files = \array_diff(\scandir($dir), ['.', '..']); + foreach ($files as $file) { + if (\is_dir("$dir/$file")) { + self::recurseRmdir("$dir/$file"); + } else { + \unlink("$dir/$file"); + } + } + return \rmdir($dir); + } + return true; + } + + /** + * On Eos storage backend when the user data is cleared after test run + * Running another test immediately fails. So Send this request to create user home directory + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $xRequestId + * + * @return void + * @throws GuzzleException + */ + public static function createEOSStorageHome( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $xRequestId = '' + ):void { + HttpRequestHelper::get( + $baseUrl . "/ocs/v2.php/apps/notifications/api/v1/notifications", + $xRequestId, + $user, + $password + ); + } +} diff --git a/tests/TestHelpers/OcsApiHelper.php b/tests/TestHelpers/OcsApiHelper.php new file mode 100644 index 00000000000..6e0623f42a2 --- /dev/null +++ b/tests/TestHelpers/OcsApiHelper.php @@ -0,0 +1,103 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Helper to make requests to the OCS API + * + * @author Artur Neumann + * + */ +class OcsApiHelper { + /** + * @param string|null $baseUrl + * @param string|null $user if set to null no authentication header will be sent + * @param string|null $password + * @param string|null $method HTTP Method + * @param string|null $path + * @param string|null $xRequestId + * @param mixed $body array of key, value pairs e.g ['value' => 'yes'] + * @param int|null $ocsApiVersion (1|2) default 2 + * @param array|null $headers + * + * @return ResponseInterface + * @throws GuzzleException + */ + public static function sendRequest( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $method, + ?string $path, + ?string $xRequestId = '', + $body = [], + ?int $ocsApiVersion = 2, + ?array $headers = [] + ):ResponseInterface { + $fullUrl = $baseUrl; + if (\substr($fullUrl, -1) !== '/') { + $fullUrl .= '/'; + } + $fullUrl .= "ocs/v{$ocsApiVersion}.php" . $path; + $headers['OCS-APIREQUEST'] = true; + return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers, $body); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $method HTTP Method + * @param string|null $path + * @param string|null $xRequestId + * @param mixed $body array of key, value pairs e.g ['value' => 'yes'] + * @param int|null $ocsApiVersion (1|2) default 2 + * @param array|null $headers + * + * @return RequestInterface + */ + public static function createOcsRequest( + ?string $baseUrl, + ?string $method, + ?string $path, + ?string $xRequestId = '', + $body = [], + ?int $ocsApiVersion = 2, + ?array $headers = [] + ):RequestInterface { + $fullUrl = $baseUrl; + if (\substr($fullUrl, -1) !== '/') { + $fullUrl .= '/'; + } + $fullUrl .= "ocs/v{$ocsApiVersion}.php" . $path; + return HttpRequestHelper::createRequest( + $fullUrl, + $xRequestId, + $method, + $headers, + $body + ); + } +} diff --git a/tests/TestHelpers/SetupHelper.php b/tests/TestHelpers/SetupHelper.php new file mode 100644 index 00000000000..16e7d043ea3 --- /dev/null +++ b/tests/TestHelpers/SetupHelper.php @@ -0,0 +1,1268 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Behat\Testwork\Hook\Scope\HookScope; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Exception\ServerException; +use Exception; +use Psr\Http\Message\ResponseInterface; +use SimpleXMLElement; + +/** + * Helper to setup UI / Integration tests + * + * @author Artur Neumann + * + */ +class SetupHelper extends \PHPUnit\Framework\Assert { + /** + * @var string + */ + private static $ocPath = null; + /** + * @var string + */ + private static $baseUrl = null; + /** + * @var string + */ + private static $adminUsername = null; + /** + * @var string + */ + private static $adminPassword = null; + + /** + * creates a user + * + * @param string|null $userName + * @param string|null $password + * @param string|null $xRequestId + * @param string|null $displayName + * @param string|null $email + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function createUser( + ?string $userName, + ?string $password, + ?string $xRequestId = '', + ?string $displayName = null, + ?string $email = null + ):array { + $occCommand = ['user:add', '--password-from-env']; + if ($displayName !== null) { + $occCommand = \array_merge($occCommand, ["--display-name", $displayName]); + } + if ($email !== null) { + $occCommand = \array_merge($occCommand, ["--email", $email]); + } + \putenv("OC_PASS=" . $password); + return self::runOcc( + \array_merge($occCommand, [$userName]), + $xRequestId + ); + } + + /** + * deletes a user + * + * @param string|null $userName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function deleteUser( + ?string $userName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['user:delete', $userName], + $xRequestId + ); + } + + /** + * + * @param string|null $userName + * @param string|null $app + * @param string|null $key + * @param string|null $value + * @param string|null $xRequestId + * + * @return string[] + * @throws Exception + */ + public static function changeUserSetting( + ?string $userName, + ?string $app, + ?string $key, + ?string $value, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['user:setting', '--value ' . $value, $userName, $app, $key], + $xRequestId + ); + } + + /** + * creates a group + * + * @param string|null $groupName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function createGroup( + ?string $groupName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['group:add', $groupName], + $xRequestId + ); + } + + /** + * adds an existing user to a group, creating the group if it does not exist + * + * @param string|null $groupName + * @param string|null $userName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function addUserToGroup( + ?string $groupName, + ?string $userName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['group:add-member', '--member', $userName, $groupName], + $xRequestId + ); + } + + /** + * removes a user from a group + * + * @param string|null $groupName + * @param string|null $userName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function removeUserFromGroup( + ?string $groupName, + ?string $userName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['group:remove-member', '--member', $userName, $groupName], + $xRequestId + ); + } + + /** + * deletes a group + * + * @param string|null $groupName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function deleteGroup( + ?string $groupName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['group:delete', $groupName], + $xRequestId + ); + } + + /** + * + * @param string|null $xRequestId + * + * @return string[] + * @throws Exception + */ + public static function getGroups( + ?string $xRequestId = '' + ):array { + return \json_decode( + self::runOcc( + ['group:list', '--output=json'], + $xRequestId + )['stdOut'] + ); + } + /** + * + * @param HookScope $scope + * + * @return array of suite context parameters + */ + public static function getSuiteParameters(HookScope $scope):array { + return $scope->getEnvironment()->getSuite() + ->getSettings() ['context'] ['parameters']; + } + + /** + * Fixup OC path so that it always starts with a "/" and does not end with + * a "/". + * + * @param string|null $ocPath + * + * @return string + */ + private static function normaliseOcPath(?string $ocPath):string { + return '/' . \trim($ocPath, '/'); + } + + /** + * + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return void + */ + public static function init( + ?string $adminUsername, + ?string $adminPassword, + ?string $baseUrl, + ?string $ocPath + ): void { + foreach (\func_get_args() as $variableToCheck) { + if (!\is_string($variableToCheck)) { + throw new \InvalidArgumentException( + "mandatory argument missing or wrong type ($variableToCheck => " + . \gettype($variableToCheck) . ")" + ); + } + } + self::$adminUsername = $adminUsername; + self::$adminPassword = $adminPassword; + self::$baseUrl = \rtrim($baseUrl, '/'); + self::$ocPath = self::normaliseOcPath($ocPath); + } + + /** + * + * @return string path to the testing app occ endpoint + * @throws Exception if ocPath has not been set yet + */ + public static function getOcPath():?string { + if (self::$ocPath === null) { + throw new Exception( + "getOcPath called before ocPath is set by init" + ); + } + + return self::$ocPath; + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $xRequestId + * + * @return SimpleXMLElement + * @throws GuzzleException + */ + public static function getSysInfo( + ?string $baseUrl, + ?string $adminUsername, + ?string $adminPassword, + ?string $xRequestId = '' + ):SimpleXMLElement { + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "GET", + "/apps/testing/api/v1/sysinfo", + $xRequestId + ); + if ($result->getStatusCode() !== 200) { + throw new \Exception( + "could not get sysinfo " . $result->getReasonPhrase() + ); + } + return HttpRequestHelper::getResponseXml($result, __METHOD__)->data; + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $xRequestId + * + * @return string + * @throws GuzzleException + */ + public static function getServerRoot( + ?string $baseUrl, + ?string $adminUsername, + ?string $adminPassword, + ?string $xRequestId = '' + ):string { + $sysInfo = self::getSysInfo( + $baseUrl, + $adminUsername, + $adminPassword, + $xRequestId + ); + // server_root is a SimpleXMLElement object that "wraps" a string. + /// We want the bare string. + return (string) $sysInfo->server_root; + } + + /** + * @param string|null $adminUsername + * @param string|null $callerName + * + * @return string + * @throws Exception + */ + private static function checkAdminUsername(?string $adminUsername, ?string $callerName):?string { + if (self::$adminUsername === null + && $adminUsername === null + ) { + throw new Exception( + "$callerName called without adminUsername - pass the username or call SetupHelper::init" + ); + } + if ($adminUsername === null) { + $adminUsername = self::$adminUsername; + } + return $adminUsername; + } + + /** + * @param string|null $adminPassword + * @param string|null $callerName + * + * @return string + * @throws Exception + */ + private static function checkAdminPassword(?string $adminPassword, ?string $callerName):string { + if (self::$adminPassword === null + && $adminPassword === null + ) { + throw new Exception( + "$callerName called without adminPassword - pass the password or call SetupHelper::init" + ); + } + if ($adminPassword === null) { + $adminPassword = self::$adminPassword; + } + return $adminPassword; + } + + /** + * @param string|null $baseUrl + * @param string|null $callerName + * + * @return string + * @throws Exception + */ + private static function checkBaseUrl(?string $baseUrl, ?string $callerName):?string { + if (self::$baseUrl === null + && $baseUrl === null + ) { + throw new Exception( + "$callerName called without baseUrl - pass the baseUrl or call SetupHelper::init" + ); + } + if ($baseUrl === null) { + $baseUrl = self::$baseUrl; + } + return $baseUrl; + } + + /** + * + * @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo' + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public static function mkDirOnServer( + ?string $dirPathFromServerRoot, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):void { + $baseUrl = self::checkBaseUrl($baseUrl, "mkDirOnServer"); + $adminUsername = self::checkAdminUsername($adminUsername, "mkDirOnServer"); + $adminPassword = self::checkAdminPassword($adminPassword, "mkDirOnServer"); + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "POST", + "/apps/testing/api/v1/dir", + $xRequestId, + ['dir' => $dirPathFromServerRoot] + ); + + if ($result->getStatusCode() !== 200) { + throw new \Exception( + "could not create directory $dirPathFromServerRoot " . $result->getReasonPhrase() + ); + } + } + + /** + * + * @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo' + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public static function rmDirOnServer( + ?string $dirPathFromServerRoot, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):void { + $baseUrl = self::checkBaseUrl($baseUrl, "rmDirOnServer"); + $adminUsername = self::checkAdminUsername($adminUsername, "rmDirOnServer"); + $adminPassword = self::checkAdminPassword($adminPassword, "rmDirOnServer"); + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "DELETE", + "/apps/testing/api/v1/dir", + $xRequestId, + ['dir' => $dirPathFromServerRoot] + ); + + if ($result->getStatusCode() !== 200) { + throw new \Exception( + "could not delete directory $dirPathFromServerRoot " . $result->getReasonPhrase() + ); + } + } + + /** + * + * @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml' + * @param string|null $content + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return void + * @throws GuzzleException + */ + public static function createFileOnServer( + ?string $filePathFromServerRoot, + ?string $content, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):void { + $baseUrl = self::checkBaseUrl($baseUrl, "createFileOnServer"); + $adminUsername = self::checkAdminUsername($adminUsername, "createFileOnServer"); + $adminPassword = self::checkAdminPassword($adminPassword, "createFileOnServer"); + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "POST", + "/apps/testing/api/v1/file", + $xRequestId, + [ + 'file' => $filePathFromServerRoot, + 'content' => $content + ] + ); + + if ($result->getStatusCode() !== 200) { + throw new \Exception( + "could not create file $filePathFromServerRoot " . $result->getReasonPhrase() + ); + } + } + + /** + * + * @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml' + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public static function deleteFileOnServer( + ?string $filePathFromServerRoot, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):void { + $baseUrl = self::checkBaseUrl($baseUrl, "deleteFileOnServer"); + $adminUsername = self::checkAdminUsername($adminUsername, "deleteFileOnServer"); + $adminPassword = self::checkAdminPassword($adminPassword, "deleteFileOnServer"); + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "DELETE", + "/apps/testing/api/v1/file", + $xRequestId, + [ + 'file' => $filePathFromServerRoot + ] + ); + + if ($result->getStatusCode() !== 200) { + throw new \Exception( + "could not delete file $filePathFromServerRoot " . $result->getReasonPhrase() + ); + } + } + + /** + * @param string|null $fileInCore e.g. 'app2/myapp/appinfo/info.xml' + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return string + * @throws GuzzleException + * @throws Exception + */ + public static function readFileFromServer( + ?string $fileInCore, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):string { + $baseUrl = self::checkBaseUrl($baseUrl, "readFile"); + $adminUsername = self::checkAdminUsername( + $adminUsername, + "readFile" + ); + $adminPassword = self::checkAdminPassword( + $adminPassword, + "readFile" + ); + + $response = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + 'GET', + "/apps/testing/api/v1/file?file={$fileInCore}", + $xRequestId + ); + self::assertSame( + 200, + $response->getStatusCode(), + "Failed to read the file {$fileInCore}" + ); + $localContent = HttpRequestHelper::getResponseXml($response, __METHOD__); + $localContent = (string)$localContent->data->element->contentUrlEncoded; + return \urldecode($localContent); + } + + /** + * returns the content of a file in a skeleton folder + * + * @param string|null $fileInSkeletonFolder + * @param string|null $xRequestId + * @param string|null $baseUrl + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return string content of the file + * @throws GuzzleException + * @throws Exception + */ + public static function readSkeletonFile( + ?string $fileInSkeletonFolder, + ?string $xRequestId = '', + ?string $baseUrl = null, + ?string $adminUsername = null, + ?string $adminPassword = null + ):string { + $baseUrl = self::checkBaseUrl($baseUrl, "readSkeletonFile"); + $adminUsername = self::checkAdminUsername( + $adminUsername, + "readSkeletonFile" + ); + $adminPassword = self::checkAdminPassword( + $adminPassword, + "readSkeletonFile" + ); + + //find the absolute path of the currently set skeletondirectory + $occResponse = self::runOcc( + ['config:system:get', 'skeletondirectory'], + $xRequestId, + $adminUsername, + $adminPassword, + $baseUrl + ); + if ((int) $occResponse['code'] !== 0) { + throw new \Exception( + "could not get current skeletondirectory. " . $occResponse['stdErr'] + ); + } + $skeletonRoot = \trim($occResponse['stdOut']); + + $fileInSkeletonFolder = \rawurlencode("$skeletonRoot/$fileInSkeletonFolder"); + $response = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + 'GET', + "/apps/testing/api/v1/file?file={$fileInSkeletonFolder}&absolute=true", + $xRequestId + ); + self::assertSame( + 200, + $response->getStatusCode(), + "Failed to read the file {$fileInSkeletonFolder}" + ); + $localContent = HttpRequestHelper::getResponseXml($response, __METHOD__); + $localContent = (string)$localContent->data->element->contentUrlEncoded; + $localContent = \urldecode($localContent); + return $localContent; + } + + /** + * enables an app + * + * @param string|null $appName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function enableApp( + ?string $appName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['app:enable', $appName], + $xRequestId + ); + } + + /** + * disables an app + * + * @param string|null $appName + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws Exception + */ + public static function disableApp( + ?string $appName, + ?string $xRequestId = '' + ):array { + return self::runOcc( + ['app:disable', $appName], + $xRequestId + ); + } + + /** + * checks if an app is currently enabled + * + * @param string|null $appName + * @param string|null $xRequestId + * + * @return bool true if enabled, false if disabled or nonexistent + * @throws Exception + */ + public static function isAppEnabled( + ?string $appName, + ?string $xRequestId = '' + ):bool { + $result = self::runOcc( + ['app:list', '^' . $appName . '$'], + $xRequestId + ); + return \strtolower(\substr($result['stdOut'], 0, 7)) === 'enabled'; + } + + /** + * lists app status (enabled or disabled) + * + * @param string|null $appName + * @param string|null $xRequestId + * + * @return bool true if the app needed to be enabled, false otherwise + * @throws Exception + */ + public static function enableAppIfNotEnabled( + ?string $appName, + ?string $xRequestId = '' + ):bool { + if (!self::isAppEnabled($appName, $xRequestId)) { + self::enableApp( + $appName, + $xRequestId + ); + return true; + } + + return false; + } + + /** + * Runs a list of occ commands at once + * + * @param array|null $commands + * @param string|null $xRequestId + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * + * @return array + * @throws GuzzleException + * @throws Exception + */ + public static function runBulkOcc( + ?array $commands, + ?string $xRequestId = '', + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null + ):array { + if (OcisHelper::isTestingOnOcisOrReva()) { + return []; + } + $baseUrl = self::checkBaseUrl($baseUrl, "runOcc"); + $adminUsername = self::checkAdminUsername($adminUsername, "runOcc"); + $adminPassword = self::checkAdminPassword($adminPassword, "runOcc"); + + if (!\is_array($commands)) { + throw new Exception("commands must be an array"); + } + + $isTestingAppEnabledText = "Is the testing app installed and enabled?\n"; + $bodies = []; + + foreach ($commands as $occ) { + if (!\array_key_exists('command', $occ)) { + throw new \InvalidArgumentException("command key is missing in array passed to runBulkOcc"); + } + + $body = [ + 'command' => \implode(' ', $occ['command']) + ]; + + if (isset($occ['envVariables'])) { + $body['env_variables'] = $occ['envVariables']; + } + \array_push($bodies, $body); + } + try { + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "POST", + "/apps/testing/api/v1/occ/bulk?format=json", + $xRequestId, + \json_encode($bodies) + ); + } catch (ServerException $e) { + throw new Exception( + "Could not execute 'occ'. " . + $isTestingAppEnabledText . + $e->getResponse()->getBody() + ); + } + $result = \json_decode($result->getBody()->getContents()); + + return $result->ocs->data; + } + + /** + * invokes an OCC command + * + * @param array|null $args anything behind "occ". + * For example: "files:transfer-ownership" + * @param string|null $xRequestId + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * @param array|null $envVariables + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws GuzzleException + * @throws Exception + */ + public static function runOcc( + ?array $args, + ?string $xRequestId = '', + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null, + ?array $envVariables = null + ):array { + if (OcisHelper::isTestingOnOcisOrReva() && !OcisHelper::isTestingParallelDeployment()) { + return ['code' => '', 'stdOut' => '', 'stdErr' => '' ]; + } + $baseUrl = self::checkBaseUrl($baseUrl, "runOcc"); + $adminUsername = self::checkAdminUsername($adminUsername, "runOcc"); + $adminPassword = self::checkAdminPassword($adminPassword, "runOcc"); + if (self::$ocPath === null + && $ocPath === null + ) { + throw new Exception( + "runOcc called without ocPath - pass the ocPath or call SetupHelper::init" + ); + } + if ($ocPath === null) { + $ocPath = self::$ocPath; + } else { + $ocPath = self::normaliseOcPath($ocPath); + } + + $body = []; + $argsString = \implode(' ', $args); + $body['command'] = $argsString; + + if ($envVariables !== null) { + $body['env_variables'] = $envVariables; + } + + $isTestingAppEnabledText = "Is the testing app installed and enabled?\n"; + + try { + $result = OcsApiHelper::sendRequest( + $baseUrl, + $adminUsername, + $adminPassword, + "POST", + $ocPath, + $xRequestId, + $body + ); + } catch (ServerException $e) { + throw new Exception( + "Could not execute 'occ'. " . + $isTestingAppEnabledText . + $e->getResponse()->getBody() + ); + } + + $return = []; + $contents = $result->getBody()->getContents(); + $resultXml = \simplexml_load_string($contents); + + if ($resultXml === false) { + $status = $result->getStatusCode(); + throw new Exception( + "Response is not valid XML after executing 'occ $argsString'. " . + "HTTP status was $status. " . + $isTestingAppEnabledText . + "Response contents were '$contents'" + ); + } + + $return['code'] = $resultXml->xpath("//ocs/data/code"); + $return['stdOut'] = $resultXml->xpath("//ocs/data/stdOut"); + $return['stdErr'] = $resultXml->xpath("//ocs/data/stdErr"); + + if (!isset($return['code'][0])) { + throw new Exception( + "Return code not found after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + if (!isset($return['stdOut'][0])) { + throw new Exception( + "Return stdOut not found after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + if (!isset($return['stdErr'][0])) { + throw new Exception( + "Return stdErr not found after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + if (!\is_a($return['code'][0], "SimpleXMLElement")) { + throw new Exception( + "Return code is not a SimpleXMLElement after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + if (!\is_a($return['stdOut'][0], "SimpleXMLElement")) { + throw new Exception( + "Return stdOut is not a SimpleXMLElement after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + if (!\is_a($return['stdErr'][0], "SimpleXMLElement")) { + throw new Exception( + "Return stdErr is not a SimpleXMLElement after executing 'occ $argsString'. " . + $isTestingAppEnabledText . + $contents + ); + } + + $return['code'] = $return['code'][0]->__toString(); + $return['stdOut'] = $return['stdOut'][0]->__toString(); + $return['stdErr'] = $return['stdErr'][0]->__toString(); + self::resetOpcache( + $baseUrl, + $adminUsername, + $adminPassword, + $xRequestId + ); + return $return; + } + + /** + * @param string $baseUrl + * @param string $user + * @param string $password + * @param string $xRequestId + * + * @return ResponseInterface|null + * @throws GuzzleException + */ + public static function resetOpcache( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $xRequestId = '' + ):?ResponseInterface { + try { + return OcsApiHelper::sendRequest( + $baseUrl, + $user, + $password, + "DELETE", + "/apps/testing/api/v1/opcache", + $xRequestId + ); + } catch (ServerException $e) { + echo "could not reset opcache, if tests fail try to set " . + "'opcache.revalidate_freq=0' in the php.ini file\n"; + } + return null; + } + + /** + * Create local storage mount + * + * @param string|null $mount (name of local storage mount) + * @param string|null $xRequestId + * + * @return string[] associated array with "code", "stdOut", "stdErr", "storageId" + * @throws GuzzleException + */ + public static function createLocalStorageMount( + ?string $mount, + ?string $xRequestId = '' + ):array { + $mountPath = TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$mount"; + SetupHelper::mkDirOnServer( + $mountPath, + $xRequestId + ); + // files_external:create requires absolute path + $serverRoot = self::getServerRoot( + self::$baseUrl, + self::$adminUsername, + self::$adminPassword, + $xRequestId + ); + $result = self::runOcc( + [ + 'files_external:create', + $mount, + 'local', + 'null::null', + '-c', + 'datadir=' . $serverRoot . '/' . $mountPath + ], + $xRequestId + ); + // stdOut should have a string like "Storage created with id 65" + $storageIdWords = \explode(" ", \trim($result['stdOut'])); + if (\array_key_exists(4, $storageIdWords)) { + $result['storageId'] = (int)$storageIdWords[4]; + } else { + // presumably something went wrong with the files_external:create command + // so return "unknown" to the caller. The result array has the command exit + // code and stdErr output etc., so the caller can process what it likes + // of that information to work out what went wrong. + $result['storageId'] = "unknown"; + } + return $result; + } + + /** + * Get a system config setting, including status code, output and standard + * error output. + * + * @param string|null $key + * @param string|null $xRequestId + * @param string|null $output e.g. json + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws GuzzleException + */ + public static function getSystemConfig( + ?string $key, + ?string $xRequestId = '', + ?string $output = null, + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ):array { + $args = []; + $args[] = 'config:system:get'; + $args[] = $key; + + if ($output !== null) { + $args[] = '--output'; + $args[] = $output; + } + + $args[] = '--no-ansi'; + + return self::runOcc( + $args, + $xRequestId, + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath + ); + } + + /** + * Set a system config setting + * + * @param string|null $key + * @param string|null $value + * @param string|null $xRequestId + * @param string|null $type e.g. boolean or json + * @param string|null $output e.g. json + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws GuzzleException + */ + public static function setSystemConfig( + ?string $key, + ?string $value, + ?string $xRequestId = '', + ?string $type = null, + ?string $output = null, + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ):array { + $args = []; + $args[] = 'config:system:set'; + $args[] = $key; + $args[] = '--value'; + $args[] = $value; + + if ($type !== null) { + $args[] = '--type'; + $args[] = $type; + } + + if ($output !== null) { + $args[] = '--output'; + $args[] = $output; + } + + $args[] = '--no-ansi'; + if ($baseUrl === null) { + $baseUrl = self::$baseUrl; + } + return self::runOcc( + $args, + $xRequestId, + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath + ); + } + + /** + * Get the value of a system config setting + * + * @param string|null $key + * @param string|null $xRequestId + * @param string|null $output e.g. json + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return string + * @throws GuzzleException if parameters have not been provided yet or the testing app is not enabled + */ + public static function getSystemConfigValue( + ?string $key, + ?string $xRequestId = '', + ?string $output = null, + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ):string { + if ($baseUrl === null) { + $baseUrl = self::$baseUrl; + } + return self::getSystemConfig( + $key, + $xRequestId, + $output, + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath + )['stdOut']; + } + + /** + * Finds all lines containing the given text + * + * @param string|null $input stdout or stderr output + * @param string|null $text text to search for + * + * @return array array of lines that matched + */ + public static function findLines(?string $input, ?string $text):array { + $results = []; + foreach (\explode("\n", $input) as $line) { + if (\strpos($line, $text) !== false) { + $results[] = $line; + } + } + return $results; + } + + /** + * Delete a system config setting + * + * @param string|null $key + * @param string|null $xRequestId + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return string[] associated array with "code", "stdOut", "stdErr" + * @throws GuzzleException if parameters have not been provided yet or the testing app is not enabled + */ + public static function deleteSystemConfig( + ?string $key, + ?string $xRequestId = '', + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ):array { + $args = []; + $args[] = 'config:system:delete'; + $args[] = $key; + + $args[] = '--no-ansi'; + if ($baseUrl === null) { + $baseUrl = self::$baseUrl; + } + return SetupHelper::runOcc( + $args, + $xRequestId, + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath + ); + } +} diff --git a/tests/TestHelpers/SharingHelper.php b/tests/TestHelpers/SharingHelper.php new file mode 100644 index 00000000000..9fcc9154e36 --- /dev/null +++ b/tests/TestHelpers/SharingHelper.php @@ -0,0 +1,261 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Exception; +use InvalidArgumentException; +use Psr\Http\Message\ResponseInterface; +use SimpleXMLElement; + +/** + * manage Shares via OCS API + * + * @author Artur Neumann + * + */ +class SharingHelper { + public const PERMISSION_TYPES = [ + 'read' => 1, + 'update' => 2, + 'create' => 4, + 'delete' => 8, + 'share' => 16, + ]; + + public const SHARE_TYPES = [ + 'user' => 0, + 'group' => 1, + 'public_link' => 3, + 'federated' => 6, + ]; + + public const SHARE_STATES = [ + 'accepted' => 0, + 'pending' => 1, + 'rejected' => 2, + 'declined' => 2, // declined is a synonym for rejected + ]; + + /** + * + * @param string $baseUrl baseURL of the ownCloud installation without /ocs. + * @param string $user user that creates the share. + * @param string $password password of the user that creates the share. + * @param string $path The path to the file or folder which should be shared. + * @param string|int $shareType The type of the share. This can be one of: + * 0 = user, 1 = group, 3 = public (link), + * 6 = federated (cloud share). + * Pass either the number or the keyword. + * @param string $xRequestId + * @param string|null $shareWith The user or group id with which the file should + * be shared. + * @param boolean|null $publicUpload Whether to allow public upload to a public + * shared folder. + * @param string|null $sharePassword The password to protect the public link + * share with. + * @param string|int|string[]|int[]|null $permissions The permissions to set on the share. + * 1 = read; 2 = update; 4 = create; + * 8 = delete; 16 = share + * (default: 31, for public shares: 1) + * Pass either the (total) number or array of numbers, + * or any of the above keywords or array of keywords. + * @param string|null $linkName A (human-readable) name for the share, + * which can be up to 64 characters in length. + * @param string|null $expireDate An expire date for public link shares. + * This argument expects a date string + * in the format 'YYYY-MM-DD'. + * @param int $ocsApiVersion + * @param int $sharingApiVersion + * @param string $sharingApp + * + * @return ResponseInterface + * @throws InvalidArgumentException + */ + public static function createShare( + string $baseUrl, + string $user, + string $password, + string $path, + $shareType, + string $xRequestId = '', + ?string $shareWith = null, + ?bool $publicUpload = false, + string $sharePassword = null, + $permissions = null, + ?string $linkName = null, + ?string $expireDate = null, + int $ocsApiVersion = 1, + int $sharingApiVersion = 1, + string $sharingApp = 'files_sharing' + ): ResponseInterface { + $fd = []; + foreach ([$path, $baseUrl, $user, $password] as $variableToCheck) { + if (!\is_string($variableToCheck)) { + throw new InvalidArgumentException( + "mandatory argument missing or wrong type ($variableToCheck => " + . \gettype($variableToCheck) . ")" + ); + } + } + + if ($permissions !== null) { + $fd['permissions'] = self::getPermissionSum($permissions); + } + + if (!\in_array($ocsApiVersion, [1, 2], true)) { + throw new InvalidArgumentException( + "invalid ocsApiVersion ($ocsApiVersion)" + ); + } + if (!\in_array($sharingApiVersion, [1, 2], true)) { + throw new InvalidArgumentException( + "invalid sharingApiVersion ($sharingApiVersion)" + ); + } + + $fullUrl = $baseUrl; + if (\substr($fullUrl, -1) !== '/') { + $fullUrl .= '/'; + } + $fullUrl .= "ocs/v{$ocsApiVersion}.php/apps/{$sharingApp}/api/v{$sharingApiVersion}/shares"; + + $fd['path'] = $path; + $fd['shareType'] = self::getShareType($shareType); + + if ($shareWith !== null) { + $fd['shareWith'] = $shareWith; + } + if ($publicUpload !== null) { + $fd['publicUpload'] = (bool) $publicUpload; + } + if ($sharePassword !== null) { + $fd['password'] = $sharePassword; + } + if ($linkName !== null) { + $fd['name'] = $linkName; + } + if ($expireDate !== null) { + $fd['expireDate'] = $expireDate; + } + $headers = ['OCS-APIREQUEST' => 'true']; + + return HttpRequestHelper::post( + $fullUrl, + $xRequestId, + $user, + $password, + $headers, + $fd + ); + } + + /** + * calculates the permission sum (int) from given permissions + * permissions can be passed in as int, string or array of int or string + * 'read' => 1 + * 'update' => 2 + * 'create' => 4 + * 'delete' => 8 + * 'share' => 16 + * + * @param string[]|string|int|int[] $permissions + * + * @return int + * @throws InvalidArgumentException + * + */ + public static function getPermissionSum($permissions):int { + if (\is_numeric($permissions)) { + // Allow any permission number so that test scenarios can + // specifically test invalid permission values + return (int) $permissions; + } + if (!\is_array($permissions)) { + $permissions = [$permissions]; + } + $permissionSum = 0; + foreach ($permissions as $permission) { + if (\array_key_exists($permission, self::PERMISSION_TYPES)) { + $permissionSum += self::PERMISSION_TYPES[$permission]; + } elseif (\in_array($permission, self::PERMISSION_TYPES, true)) { + $permissionSum += (int) $permission; + } else { + throw new InvalidArgumentException( + "invalid permission type ($permission)" + ); + } + } + if ($permissionSum < 1 || $permissionSum > 31) { + throw new InvalidArgumentException( + "invalid permission total ($permissionSum)" + ); + } + return $permissionSum; + } + + /** + * returns the share type number corresponding to the share type keyword + * + * @param string|int $shareType a keyword from SHARE_TYPES or a share type number + * + * @return int + * @throws InvalidArgumentException + * + */ + public static function getShareType($shareType):int { + if (\array_key_exists($shareType, self::SHARE_TYPES)) { + return self::SHARE_TYPES[$shareType]; + } else { + if (\ctype_digit($shareType)) { + $shareType = (int) $shareType; + } + $key = \array_search($shareType, self::SHARE_TYPES, true); + if ($key !== false) { + return self::SHARE_TYPES[$key]; + } + throw new InvalidArgumentException( + "invalid share type ($shareType)" + ); + } + } + + /** + * + * @param SimpleXMLElement $responseXmlObject + * @param string $errorMessage + * + * @return string + * @throws Exception + * + */ + public static function getLastShareIdFromResponse( + SimpleXMLElement $responseXmlObject, + string $errorMessage = "cannot find share id in response" + ):string { + $xmlPart = $responseXmlObject->xpath("//data/element[last()]/id"); + + if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) { + throw new Exception($errorMessage); + } + return $xmlPart[0]->__toString(); + } +} diff --git a/tests/TestHelpers/SpaceNotFoundException.php b/tests/TestHelpers/SpaceNotFoundException.php new file mode 100644 index 00000000000..4a576e3919e --- /dev/null +++ b/tests/TestHelpers/SpaceNotFoundException.php @@ -0,0 +1,14 @@ + + * + */ +namespace TestHelpers; +use Exception; + +/** + * Class SpaceNotFoundException + * Exception when space id for a user is not found + */ +class SpaceNotFoundException extends Exception { +} diff --git a/tests/TestHelpers/TagsHelper.php b/tests/TestHelpers/TagsHelper.php new file mode 100644 index 00000000000..21f23facc31 --- /dev/null +++ b/tests/TestHelpers/TagsHelper.php @@ -0,0 +1,352 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Psr\Http\Message\ResponseInterface; +use Exception; +use SimpleXMLElement; + +/** + * Helper to administer Tags + * + * @author Artur Neumann + * + */ +class TagsHelper extends \PHPUnit\Framework\Assert { + /** + * tags a file + * + * @param string|null $baseUrl + * @param string|null $taggingUser + * @param string|null $password + * @param string|null $tagName + * @param string|null $fileName + * @param string|null $xRequestId + * @param string|null $fileOwner + * @param string|null $fileOwnerPassword + * @param int|null $davPathVersionToUse (1|2) + * @param string|null $adminUsername + * @param string|null $adminPassword + * + * @return ResponseInterface + * @throws Exception + */ + public static function tag( + ?string $baseUrl, + ?string $taggingUser, + ?string $password, + ?string $tagName, + ?string $fileName, + ?string $xRequestId = '', + ?string $fileOwner = null, + ?string $fileOwnerPassword = null, + ?int $davPathVersionToUse = 2, + ?string $adminUsername = null, + ?string $adminPassword = null + ):ResponseInterface { + if ($fileOwner === null) { + $fileOwner = $taggingUser; + } + + if ($fileOwnerPassword === null) { + $fileOwnerPassword = $password; + } + + $fileID = WebDavHelper::getFileIdForPath( + $baseUrl, + $fileOwner, + $fileOwnerPassword, + $fileName, + $xRequestId + ); + + try { + $tag = self::requestTagByDisplayName( + $baseUrl, + $taggingUser, + $password, + $tagName, + $xRequestId + ); + } catch (Exception $e) { + //the tag might be not accessible by the user + //if we still want to find it, we need to try as admin + if ($adminUsername !== null && $adminPassword !== null) { + $tag = self::requestTagByDisplayName( + $baseUrl, + $adminUsername, + $adminPassword, + $tagName, + $xRequestId + ); + } else { + throw $e; + } + } + $tagID = self::getTagIdFromTagData($tag); + $path = '/systemtags-relations/files/' . $fileID . '/' . $tagID; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $taggingUser, + $password, + "PUT", + $path, + null, + $xRequestId, + null, + $davPathVersionToUse, + "systemtags" + ); + return $response; + } + + /** + * @param SimpleXMLElement $tagData + * + * @return int + */ + public static function getTagIdFromTagData(SimpleXMLElement $tagData):int { + $tagID = $tagData->xpath(".//oc:id"); + self::assertArrayHasKey( + 0, + $tagID, + "cannot find id of tag" + ); + + return (int) $tagID[0]->__toString(); + } + + /** + * get all tags of a user + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $xRequestId + * @param bool $withGroups + * + * @return SimpleXMLElement + * @throws Exception + */ + public static function requestTagsForUser( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $xRequestId = '', + ?bool $withGroups = false + ):SimpleXMLElement { + $properties = [ + 'oc:id', + 'oc:display-name', + 'oc:user-visible', + 'oc:user-assignable', + 'oc:can-assign' + ]; + if ($withGroups) { + \array_push($properties, 'oc:groups'); + } + $response = WebDavHelper::propfind( + $baseUrl, + $user, + $password, + '/systemtags/', + $properties, + $xRequestId, + '1', + "systemtags" + ); + return HttpRequestHelper::getResponseXml($response, __METHOD__); + } + + /** + * find a tag by its name + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $tagDisplayName + * @param string|null $xRequestId + * @param bool $withGroups + * + * @return SimpleXMLElement + * @throws Exception + */ + public static function requestTagByDisplayName( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $tagDisplayName, + ?string $xRequestId = '', + ?bool $withGroups = false + ):SimpleXMLElement { + $tagList = self::requestTagsForUser( + $baseUrl, + $user, + $password, + $xRequestId, + $withGroups + ); + $tagData = $tagList->xpath( + "//d:prop//oc:display-name[text() ='$tagDisplayName']/.." + ); + self::assertArrayHasKey( + 0, + $tagData, + "cannot find 'oc:display-name' property with text '$tagDisplayName'" + ); + return $tagData[0]; + } + + /** + * + * @param string|null $baseUrl see: self::makeDavRequest() + * @param string|null $user + * @param string|null $password + * @param string|null $name + * @param string|null $xRequestId + * @param string|null $userVisible "true", "1" or "false", "0" + * @param string|null $userAssignable "true", "1" or "false", "0" + * @param string|null $userEditable "true", "1" or "false", "0" + * @param string|null $groups separated by "|" + * @param int|null $davPathVersionToUse (1|2) + * + * @return ResponseInterface + * @link self::makeDavRequest() + */ + public static function createTag( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $name, + ?string $xRequestId = '', + ?string $userVisible = "true", + ?string $userAssignable = "true", + ?string $userEditable = "false", + ?string $groups = null, + ?int $davPathVersionToUse = 2 + ):ResponseInterface { + $tagsPath = '/systemtags/'; + $body = [ + 'name' => $name, + 'userVisible' => $userVisible, + 'userAssignable' => $userAssignable, + 'userEditable' => $userEditable + ]; + + if ($groups !== null) { + $body['groups'] = $groups; + } + + return WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "POST", + $tagsPath, + ['Content-Type' => 'application/json',], + $xRequestId, + \json_encode($body), + $davPathVersionToUse, + "systemtags" + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param int|null $tagID + * @param string|null $xRequestId + * @param int|null $davPathVersionToUse (1|2) + * + * @return ResponseInterface + */ + public static function deleteTag( + ?string $baseUrl, + ?string $user, + ?string $password, + ?int $tagID, + ?string $xRequestId = '', + ?int $davPathVersionToUse = 1 + ):ResponseInterface { + $tagsPath = '/systemtags/' . $tagID; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "DELETE", + $tagsPath, + [], + $xRequestId, + null, + $davPathVersionToUse, + "systemtags" + ); + return $response; + } + + /** + * Validate the keyword(s) used for the type of tag + * Tags can be "normal", "not user-assignable", "not user-visible" or "static" + * That determines the tag attributes which are set when creating the tag. + * + * When creating the tag, the attributes can be enabled/disabled by specifying + * either "true"/"false" or "1"/"0" in the request. Choose this "request style" + * by passing the $useTrueFalseStrings parameter. + * + * @param string|null $type + * @param boolean $useTrueFalseStrings use the strings "true"/"false" else "1"/"0" + * + * @return string[] + * @throws Exception + */ + public static function validateTypeOfTag(?string $type, ?bool $useTrueFalseStrings = true):array { + if ($useTrueFalseStrings) { + $trueValue = "true"; + $falseValue = "false"; + } else { + $trueValue = "1"; + $falseValue = "0"; + } + $userVisible = $trueValue; + $userAssignable = $trueValue; + $userEditable = $trueValue; + switch ($type) { + case 'normal': + break; + case 'not user-assignable': + $userAssignable = $falseValue; + break; + case 'not user-visible': + $userVisible = $falseValue; + break; + case 'static': + $userEditable = $falseValue; + break; + default: + throw new Exception('Unsupported type'); + } + + return [$userVisible, $userAssignable, $userEditable]; + } +} diff --git a/tests/TestHelpers/TranslationHelper.php b/tests/TestHelpers/TranslationHelper.php new file mode 100644 index 00000000000..bc262b47398 --- /dev/null +++ b/tests/TestHelpers/TranslationHelper.php @@ -0,0 +1,46 @@ + + * @copyright Copyright (c) 2021 Talank Baral talank@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +namespace TestHelpers; + +/** + * Class TranslationHelper + * + * Helper functions that are needed to run tests on different languages + * + * @package TestHelpers + */ +class TranslationHelper { + /** + * @param string|null $language + * + * @return string|null + */ + public static function getLanguage(?string $language): ?string { + if (!isset($language)) { + if (\getenv('OC_LANGUAGE') !== false) { + $language = \getenv('OC_LANGUAGE'); + } + } + return $language; + } +} diff --git a/tests/TestHelpers/UploadHelper.php b/tests/TestHelpers/UploadHelper.php new file mode 100644 index 00000000000..1ff63cfa55b --- /dev/null +++ b/tests/TestHelpers/UploadHelper.php @@ -0,0 +1,318 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Psr\Http\Message\ResponseInterface; + +/** + * Helper for Uploads + * + * @author Artur Neumann + * + */ +class UploadHelper extends \PHPUnit\Framework\Assert { + /** + * + * @param string|null $baseUrl URL of owncloud + * e.g. http://localhost:8080 + * should include the subfolder + * if owncloud runs in a subfolder + * e.g. http://localhost:8080/owncloud-core + * @param string|null $user + * @param string|null $password + * @param string|null $source + * @param string|null $destination + * @param string|null $xRequestId + * @param array|null $headers + * @param int|null $davPathVersionToUse (1|2) + * @param int|null $chunkingVersion (1|2|null) + * if set to null chunking will not be used + * @param int|null $noOfChunks how many chunks do we want to upload + * + * @return ResponseInterface + */ + public static function upload( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $source, + ?string $destination, + ?string $xRequestId = '', + ?array $headers = [], + ?int $davPathVersionToUse = 1, + ?int $chunkingVersion = null, + ?int $noOfChunks = 1 + ): ResponseInterface { + //simple upload with no chunking + if ($chunkingVersion === null) { + $data = \file_get_contents($source); + return WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "PUT", + $destination, + $headers, + $xRequestId, + $data, + $davPathVersionToUse + ); + } else { + //prepare chunking + $chunks = self::chunkFile($source, $noOfChunks); + $chunkingId = 'chunking-' . (string)\rand(1000, 9999); + $v2ChunksDestination = '/uploads/' . $user . '/' . $chunkingId; + } + + //prepare chunking version specific stuff + if ($chunkingVersion === 1) { + $headers['OC-Chunked'] = '1'; + } elseif ($chunkingVersion === 2) { + $result = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + 'MKCOL', + $v2ChunksDestination, + $headers, + $xRequestId, + null, + $davPathVersionToUse, + "uploads" + ); + if ($result->getStatusCode() >= 400) { + return $result; + } + } + + //upload chunks + foreach ($chunks as $index => $chunk) { + if ($chunkingVersion === 1) { + $filename = $destination . "-" . $chunkingId . "-" . + \count($chunks) . '-' . ( string ) $index; + $davRequestType = "files"; + } elseif ($chunkingVersion === 2) { + $filename = $v2ChunksDestination . '/' . (string)($index); + $davRequestType = "uploads"; + } + $result = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "PUT", + $filename, + $headers, + $xRequestId, + $chunk, + $davPathVersionToUse, + $davRequestType + ); + if ($result->getStatusCode() >= 400) { + return $result; + } + } + //finish upload for new chunking + if ($chunkingVersion === 2) { + $source = $v2ChunksDestination . '/.file'; + $headers['Destination'] = $baseUrl . "/" . + WebDavHelper::getDavPath($user, $davPathVersionToUse) . + $destination; + $result = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + 'MOVE', + $source, + $headers, + $xRequestId, + null, + $davPathVersionToUse, + "uploads" + ); + if ($result->getStatusCode() >= 400) { + return $result; + } + } + return $result; + } + + /** + * Upload the same file multiple times with different mechanisms. + * + * @param string|null $baseUrl URL of owncloud + * @param string|null $user user who uploads + * @param string|null $password + * @param string|null $source source file path + * @param string|null $destination destination path on the server + * @param string|null $xRequestId + * @param bool $overwriteMode when false creates separate files to test uploading brand new files, + * when true it just overwrites the same file over and over again with the same name + * @param string|null $exceptChunkingType empty string or "old" or "new" + * + * @return array of ResponseInterface + */ + public static function uploadWithAllMechanisms( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $source, + ?string $destination, + ?string $xRequestId = '', + ?bool $overwriteMode = false, + ?string $exceptChunkingType = '' + ):array { + $responses = []; + foreach ([1, 2] as $davPathVersion) { + if ($davPathVersion === 1) { + $davHuman = 'old'; + } else { + $davHuman = 'new'; + } + + switch ($exceptChunkingType) { + case 'old': + $exceptChunkingVersion = 1; + break; + case 'new': + $exceptChunkingVersion = 2; + break; + default: + $exceptChunkingVersion = -1; + break; + } + + foreach ([null, 1, 2] as $chunkingVersion) { + if ($chunkingVersion === $exceptChunkingVersion) { + continue; + } + $valid = WebDavHelper::isValidDavChunkingCombination( + $davPathVersion, + $chunkingVersion + ); + if ($valid === false) { + continue; + } + $finalDestination = $destination; + if (!$overwriteMode && $chunkingVersion !== null) { + $finalDestination .= "-{$davHuman}dav-{$davHuman}chunking"; + } elseif (!$overwriteMode && $chunkingVersion === null) { + $finalDestination .= "-{$davHuman}dav-regular"; + } + $responses[] = self::upload( + $baseUrl, + $user, + $password, + $source, + $finalDestination, + $xRequestId, + [], + $davPathVersion, + $chunkingVersion, + 2 + ); + } + } + return $responses; + } + + /** + * cut the file in multiple chunks + * returns an array of chunks with the content of the file + * + * @param string|null $file + * @param int|null $noOfChunks + * + * @return array $string + */ + public static function chunkFile(?string $file, ?int $noOfChunks = 1):array { + $size = \filesize($file); + $chunkSize = \ceil($size / $noOfChunks); + $chunks = []; + $fp = \fopen($file, 'r'); + while (!\feof($fp) && \ftell($fp) < $size) { + $chunks[] = \fread($fp, (int)$chunkSize); + } + \fclose($fp); + if (\count($chunks) === 0) { + // chunk an empty file + $chunks[] = ''; + } + return $chunks; + } + + /** + * creates a File with a specific size + * + * @param string|null $name full path of the file to create + * @param int|null $size + * + * @return void + */ + public static function createFileSpecificSize(?string $name, ?int $size):void { + if (\file_exists($name)) { + \unlink($name); + } + $file = \fopen($name, 'w'); + \fseek($file, \max($size - 1, 0), SEEK_CUR); + if ($size) { + \fwrite($file, 'a'); // write a dummy char at SIZE position + } + \fclose($file); + self::assertEquals( + 1, + \file_exists($name) + ); + self::assertEquals( + $size, + \filesize($name) + ); + } + + /** + * creates a File with a specific text content + * + * @param string|null $name full path of the file to create + * @param string|null $text + * + * @return void + */ + public static function createFileWithText(?string $name, ?string $text):void { + $file = \fopen($name, 'w'); + \fwrite($file, $text); + \fclose($file); + self::assertEquals( + 1, + \file_exists($name) + ); + } + + /** + * get the path of a file from FilesForUpload directory + * + * @param string|null $name name of the file to upload + * + * @return string + */ + public static function getUploadFilesDir(?string $name):string { + return \getenv("FILES_FOR_UPLOAD") . $name; + } +} diff --git a/tests/TestHelpers/UserHelper.php b/tests/TestHelpers/UserHelper.php new file mode 100644 index 00000000000..8dda97fdc24 --- /dev/null +++ b/tests/TestHelpers/UserHelper.php @@ -0,0 +1,375 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Exception; +use GuzzleHttp\Exception\ClientException; +use Psr\Http\Message\ResponseInterface; + +/** + * Helper to administrate users (and groups) through the provisioning API + * + * @author Artur Neumann + * + */ +class UserHelper { + /** + * + * @param string|null $baseUrl + * @param string $user + * @param string $key + * @param string $value + * @param string $adminUser + * @param string $adminPassword + * @param string $xRequestId + * @param int|null $ocsApiVersion + * + * @return ResponseInterface + */ + public static function editUser( + ?string $baseUrl, + string $user, + string $key, + string $value, + string $adminUser, + string $adminPassword, + string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "PUT", + "/cloud/users/" . $user, + $xRequestId, + ["key" => $key, "value" => $value], + $ocsApiVersion + ); + } + + /** + * Send batch requests to edit the user. + * This will send multiple requests in parallel to the server which will be faster in comparison to waiting for each request to complete. + * + * @param string|null $baseUrl + * @param array|null $editData ['user' => '', 'key' => '', 'value' => ''] + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion + * + * @return array + * @throws Exception + */ + public static function editUserBatch( + ?string $baseUrl, + ?array $editData, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):array { + $requests = []; + $client = HttpRequestHelper::createClient( + $adminUser, + $adminPassword + ); + + foreach ($editData as $data) { + $path = "/cloud/users/" . $data['user']; + $body = ["key" => $data['key'], 'value' => $data["value"]]; + // Create the OCS API requests and push them to an array. + \array_push( + $requests, + OcsApiHelper::createOcsRequest( + $baseUrl, + 'PUT', + $path, + $xRequestId, + $body + ) + ); + } + // Send the array of requests at once in parallel. + $results = HttpRequestHelper::sendBatchRequest($requests, $client); + + foreach ($results as $e) { + if ($e instanceof ClientException) { + $httpStatusCode = $e->getResponse()->getStatusCode(); + $reasonPhrase = $e->getResponse()->getReasonPhrase(); + throw new Exception( + "Unexpected failure when editing a user: HTTP status $httpStatusCode HTTP reason $reasonPhrase" + ); + } + } + return $results; + } + + /** + * + * @param string|null $baseUrl + * @param string|null $userName + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion + * + * @return ResponseInterface + */ + public static function getUser( + ?string $baseUrl, + ?string $userName, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "GET", + "/cloud/users/" . $userName, + $xRequestId, + [], + $ocsApiVersion + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $userName + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion + * + * @return ResponseInterface + */ + public static function deleteUser( + ?string $baseUrl, + ?string $userName, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "DELETE", + "/cloud/users/" . $userName, + $xRequestId, + [], + $ocsApiVersion + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $group + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * + * @return ResponseInterface + */ + public static function createGroup( + ?string $baseUrl, + ?string $group, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '' + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "POST", + "/cloud/groups", + $xRequestId, + ['groupid' => $group] + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $group + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion + * + * @return ResponseInterface + */ + public static function deleteGroup( + ?string $baseUrl, + ?string $group, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):ResponseInterface { + $group = \rawurlencode($group); + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "DELETE", + "/cloud/groups/" . $group, + $xRequestId, + [], + $ocsApiVersion + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $group + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion (1|2) + * + * @return ResponseInterface + */ + public static function addUserToGroup( + ?string $baseUrl, + ?string $user, + ?string $group, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?int $ocsApiVersion = 2 + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "POST", + "/cloud/users/" . $user . "/groups", + $xRequestId, + ['groupid' => $group], + $ocsApiVersion + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $group + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param int|null $ocsApiVersion (1|2) + * + * @return ResponseInterface + */ + public static function removeUserFromGroup( + ?string $baseUrl, + ?string $user, + ?string $group, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId, + ?int $ocsApiVersion = 2 + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "DELETE", + "/cloud/users/" . $user . "/groups", + $xRequestId, + ['groupid' => $group], + $ocsApiVersion + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param string|null $search + * + * @return ResponseInterface + */ + public static function getGroups( + ?string $baseUrl, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?string $search ="" + ):ResponseInterface { + return OcsApiHelper::sendRequest( + $baseUrl, + $adminUser, + $adminPassword, + "GET", + "/cloud/groups?search=" . $search, + $xRequestId + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $adminUser + * @param string|null $adminPassword + * @param string|null $xRequestId + * @param string|null $search + * + * @return string[] + * @throws Exception + */ + public static function getGroupsAsArray( + ?string $baseUrl, + ?string $adminUser, + ?string $adminPassword, + ?string $xRequestId = '', + ?string $search = "" + ):array { + $result = self::getGroups( + $baseUrl, + $adminUser, + $adminPassword, + $xRequestId, + $search + ); + $groups = HttpRequestHelper::getResponseXml($result, __METHOD__)->xpath(".//groups")[0]; + $return = []; + foreach ($groups as $group) { + $return[] = $group->__toString(); + } + return $return; + } +} diff --git a/tests/TestHelpers/WebDavHelper.php b/tests/TestHelpers/WebDavHelper.php new file mode 100644 index 00000000000..e8d0c9905bf --- /dev/null +++ b/tests/TestHelpers/WebDavHelper.php @@ -0,0 +1,898 @@ + + * @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ +namespace TestHelpers; + +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use InvalidArgumentException; +use PHPUnit\Framework\Assert; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use DateTime; +use TestHelpers\SpaceNotFoundException; + +/** + * Helper to make WebDav Requests + * + * @author Artur Neumann + * + */ +class WebDavHelper { + public const DAV_VERSION_OLD = 1; + public const DAV_VERSION_NEW = 2; + public const DAV_VERSION_SPACES = 3; + public static $SPACE_ID_FROM_OCIS = ''; + + /** + * @var array of users with their different spaces ids + */ + public static $spacesIdRef = []; + + /** + * clear space id reference for user + * + * @param string|null $user + * + * @return void + * @throws Exception + */ + public static function removeSpaceIdReferenceForUser( + ?string $user + ):void { + if (\array_key_exists($user, self::$spacesIdRef)) { + unset(self::$spacesIdRef[$user]); + } + } + + /** + * returns the id of a file + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $path + * @param string|null $xRequestId + * @param int|null $davPathVersionToUse + * + * @return string + * @throws Exception + */ + public static function getFileIdForPath( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $path, + ?string $xRequestId = '', + ?int $davPathVersionToUse = self::DAV_VERSION_NEW + ): string { + $body + = ' + + + + + '; + $response = self::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPFIND", + $path, + null, + $xRequestId, + $body, + $davPathVersionToUse + ); + \preg_match( + '/\([^\<]*)\<\/oc:fileid\>/', + $response->getBody()->getContents(), + $matches + ); + + if (!isset($matches[1])) { + throw new Exception("could not find fileId of $path"); + } + + return $matches[1]; + } + + /** + * returns body for propfind + * + * @param array|null $properties + * + * @return string + * @throws Exception + */ + public static function getBodyForPropfind(?array $properties): string { + $propertyBody = ""; + $extraNamespaces = ""; + foreach ($properties as $namespaceString => $property) { + if (\is_int($namespaceString)) { + //default namespace prefix if the property has no array key + //also used if no prefix is given in the property value + $namespacePrefix = "d"; + } else { + //calculate the namespace prefix and namespace from the array key + $matches = []; + \preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches); + $nameSpace = $matches[2]; + $namespacePrefix = $matches[1]; + $extraNamespaces .= " xmlns:$namespacePrefix=\"$nameSpace\" "; + } + //if a namespace prefix is given in the property value use that + if (\strpos($property, ":") !== false) { + $propertyParts = \explode(":", $property); + $namespacePrefix = $propertyParts[0]; + $property = $propertyParts[1]; + } + $propertyBody .= "<$namespacePrefix:$property/>"; + } + $body = " + + $propertyBody + "; + return $body; + } + + /** + * sends a PROPFIND request + * with these registered namespaces: + * | prefix | namespace | + * | d | DAV: | + * | oc | http://owncloud.org/ns | + * | ocs | http://open-collaboration-services.org/ns | + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $path + * @param string[] $properties + * string can contain namespace prefix, + * if no prefix is given 'd:' is used as prefix + * if associated array is used then the key will be used as namespace + * @param string|null $xRequestId + * @param string|null $folderDepth + * @param string|null $type + * @param int|null $davPathVersionToUse + * @param string|null $doDavRequestAsUser + * + * @return ResponseInterface + */ + public static function propfind( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $path, + ?array $properties, + ?string $xRequestId = '', + ?string $folderDepth = '0', + ?string $type = "files", + ?int $davPathVersionToUse = self::DAV_VERSION_NEW, + ?string $doDavRequestAsUser = null + ):ResponseInterface { + $body = self::getBodyForPropfind($properties); + $folderDepth = (string) $folderDepth; + if ($folderDepth !== '0' && $folderDepth !== '1' && $folderDepth !== 'infinity') { + throw new InvalidArgumentException('Invalid depth value ' . $folderDepth); + } + $headers = ['Depth' => $folderDepth]; + return self::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPFIND", + $path, + $headers, + $xRequestId, + $body, + $davPathVersionToUse, + $type, + null, + null, + false, + null, + null, + [], + $doDavRequestAsUser + ); + } + + /** + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $path + * @param string|null $propertyName + * @param string|null $propertyValue + * @param string|null $xRequestId + * @param string|null $namespaceString string containing prefix and namespace + * e.g "x1='http://whatever.org/ns'" + * @param int|null $davPathVersionToUse + * @param string|null $type + * + * @return ResponseInterface + */ + public static function proppatch( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $path, + ?string $propertyName, + ?string $propertyValue, + ?string $xRequestId = '', + ?string $namespaceString = "oc='http://owncloud.org/ns'", + ?int $davPathVersionToUse = self::DAV_VERSION_NEW, + ?string $type="files" + ):ResponseInterface { + $matches = []; + \preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches); + $namespace = $matches[2]; + $namespacePrefix = $matches[1]; + $propertyBody = "<$namespacePrefix:$propertyName" . + " xmlns:$namespacePrefix=\"$namespace\">" . + "$propertyValue" . + ""; + $body = " + + + $propertyBody + + "; + return self::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPPATCH", + $path, + [], + $xRequestId, + $body, + $davPathVersionToUse, + $type + ); + } + + /** + * gets namespace-prefix, namespace url and propName from provided namespaceString or property + * or otherwise use default + * + * @param string|null $namespaceString + * @param string|null $property + * + * @return array + */ + public static function getPropertyWithNamespaceInfo(?string $namespaceString = "", ?string $property = ""):array { + $namespace = ""; + $namespacePrefix = ""; + if (\is_int($namespaceString)) { + //default namespace prefix if the property has no array key + //also used if no prefix is given in the property value + $namespacePrefix = "d"; + $namespace = "DAV:"; + } elseif ($namespaceString) { + //calculate the namespace prefix and namespace from the array key + $matches = []; + \preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches); + $namespacePrefix = $matches[1]; + $namespace = $matches[2]; + } + //if a namespace prefix is given in the property value use that + if ($property && \strpos($property, ":")) { + $propertyParts = \explode(":", $property); + $namespacePrefix = $propertyParts[0]; + $property = $propertyParts[1]; + } + return [$namespacePrefix, $namespace, $property]; + } + + /** + * sends HTTP request PROPPATCH method with multiple properties + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string $path + * @param array|null $propertiesArray + * @param string|null $xRequestId + * @param int|null $davPathVersion + * @param string|null $namespaceString + * @param string|null $type + * + * @return ResponseInterface + * @throws GuzzleException + */ + public static function proppatchWithMultipleProps( + ?string $baseUrl, + ?string $user, + ?string $password, + string $path, + ?array $propertiesArray, + ?string $xRequestId = '', + ?int $davPathVersion = null, + ?string $namespaceString = "oc='http://owncloud.org/ns'", + ?string $type="files" + ):ResponseInterface { + $propertyBody = ""; + foreach ($propertiesArray as $propertyArray) { + $property = $propertyArray["propertyName"]; + $value = $propertyArray["propertyValue"]; + [$namespacePrefix, $namespace, $property] = self::getPropertyWithNamespaceInfo( + $namespaceString, + $property + ); + $propertyBody .= "\n\t<$namespacePrefix:$property>" . + "$value" . + ""; + } + $body = " + + + $propertyBody + + + "; + return self::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPPATCH", + $path, + [], + $xRequestId, + $body, + $davPathVersion, + $type + ); + } + + /** + * returns the response to listing a folder (collection) + * + * @param string|null $baseUrl + * @param string|null $user + * @param string|null $password + * @param string|null $path + * @param string|null $folderDepth + * @param string|null $xRequestId + * @param string[] $properties + * @param string|null $type + * @param int|null $davPathVersionToUse + * + * @return ResponseInterface + */ + public static function listFolder( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $path, + ?string $folderDepth, + ?string $xRequestId = '', + ?array $properties = null, + ?string $type = "files", + ?int $davPathVersionToUse = self::DAV_VERSION_NEW + ):ResponseInterface { + if (!$properties) { + $properties = [ + 'getetag', 'resourcetype' + ]; + } + return self::propfind( + $baseUrl, + $user, + $password, + $path, + $properties, + $xRequestId, + $folderDepth, + $type, + $davPathVersionToUse + ); + } + + /** + * Generates UUIDv4 + * Example: 123e4567-e89b-12d3-a456-426614174000 + * + * @return string + * @throws Exception + */ + public static function generateUUIDv4():string { + // generate 16 bytes (128 bits) of random data or use the data passed into the function. + $data = random_bytes(16); + \assert(\strlen($data) == 16); + + $data[6] = \chr(\ord($data[6]) & 0x0f | 0x40); // set version to 0100 + $data[8] = \chr(\ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 + + return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); + } + + /** + * fetches personal space id for provided user + * + * @param string $baseUrl + * @param string $user + * @param string $password + * @param string $xRequestId + * + * @return string + * @throws GuzzleException + * @throws Exception + */ + public static function getPersonalSpaceIdForUser(string $baseUrl, string $user, string $password, string $xRequestId):string { + if (\array_key_exists($user, self::$spacesIdRef) && \array_key_exists("personal", self::$spacesIdRef[$user])) { + return self::$spacesIdRef[$user]["personal"]; + } + $trimmedBaseUrl = \trim($baseUrl, "/"); + $drivesPath = '/graph/v1.0/me/drives'; + $fullUrl = $trimmedBaseUrl . $drivesPath; + $response = HttpRequestHelper::get( + $fullUrl, + $xRequestId, + $user, + $password + ); + $bodyContents = $response->getBody()->getContents(); + $json = \json_decode($bodyContents); + $personalSpaceId = ''; + if ($json === null) { + // the graph endpoint did not give a useful answer + // try getting the information from the webdav endpoint + $fullUrl = $trimmedBaseUrl . '/remote.php/webdav'; + $response = HttpRequestHelper::sendRequest( + $fullUrl, + $xRequestId, + 'PROPFIND', + $user, + $password + ); + // we expect to get a multipart XML response with status 207 + $status = $response->getStatusCode(); + if ($status === 401) { + throw new SpaceNotFoundException(__METHOD__ . " Personal space not found for user " . $user); + } elseif ($status !== 207) { + throw new Exception( + __METHOD__ . " webdav propfind for user $user failed with status $status - so the personal space id cannot be discovered" + ); + } + $responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $xmlPart = $responseXmlObject->xpath("/d:multistatus/d:response[1]/d:propstat/d:prop/oc:id"); + if ($xmlPart === false) { + throw new Exception( + __METHOD__ . " oc:id not found in webdav propfind for user $user - so the personal space id cannot be discovered" + ); + } + $ocIdRawString = $xmlPart[0]->__toString(); + $separator = "!"; + if (\strpos($ocIdRawString, $separator) !== false) { + // The string is not base64-encoded, because the exclamation mark is not in the base64 alphabet. + // We expect to have a string with 2 parts separated by the exclamation mark. + // This is the format introduced in 2022-02 + // oc:id should be something like: + // "7464caf6-1799-103c-9046-c7b74deb5f63!7464caf6-1799-103c-9046-c7b74deb5f63" + // There is no encoding to decode. + $decodedId = $ocIdRawString; + } else { + // fall-back to assuming that the oc:id is base64-encoded + // That is the format used before and up to 2022-02 + // This can be removed after both the edge and master branches of cs3org/reva are using the new format. + // oc:id should be some base64 encoded string like: + // "NzQ2NGNhZjYtMTc5OS0xMDNjLTkwNDYtYzdiNzRkZWI1ZjYzOjc0NjRjYWY2LTE3OTktMTAzYy05MDQ2LWM3Yjc0ZGViNWY2Mw==" + // That should decode to something like: + // "7464caf6-1799-103c-9046-c7b74deb5f63:7464caf6-1799-103c-9046-c7b74deb5f63" + $decodedId = base64_decode($ocIdRawString); + $separator = ":"; + } + $ocIdParts = \explode($separator, $decodedId); + if (\count($ocIdParts) !== 2) { + throw new Exception( + __METHOD__ . " the oc:id $decodedId for user $user does not have 2 parts separated by '$separator', so the personal space id cannot be discovered" + ); + } + $personalSpaceId = $ocIdParts[0]; + } else { + foreach ($json->value as $spaces) { + if ($spaces->driveType === "personal") { + $personalSpaceId = $spaces->id; + break; + } + } + } + if ($personalSpaceId) { + // If env var LOG_PERSONAL_SPACE_ID is defined, then output the details of the personal space id. + // This is a useful debugging tool to have confidence that the personal space id is found correctly. + if (\getenv('LOG_PERSONAL_SPACE_ID') !== false) { + echo __METHOD__ . " personal space id of user $user is $personalSpaceId\n"; + } + self::$spacesIdRef[$user] = []; + self::$spacesIdRef[$user]["personal"] = $personalSpaceId; + return $personalSpaceId; + } else { + throw new SpaceNotFoundException(__METHOD__ . " Personal space not found for user " . $user); + } + } + + /** + * First checks if a user exist to return its space ID + * In case of any exception, it returns a fake space ID + * + * @param string $baseUrl + * @param string $user + * @param string $password + * @param string $xRequestId + * + * @return string + * @throws Exception + */ + public static function getPersonalSpaceIdForUserOrFakeIfNotFound(string $baseUrl, string $user, string $password, string $xRequestId):string { + try { + $spaceId = self::getPersonalSpaceIdForUser( + $baseUrl, + $user, + $password, + $xRequestId, + ); + } catch (SpaceNotFoundException $e) { + // if the fetch fails, and the user is not found, then a fake space id is prepared + // this is useful for testing when the personal space is of a non-existing user + $fakeSpaceId = self::generateUUIDv4(); + self::$spacesIdRef[$user]["personal"] = $fakeSpaceId; + $spaceId = $fakeSpaceId; + } + return $spaceId; + } + + /** + * sends a DAV request + * + * @param string|null $baseUrl + * URL of owncloud e.g. http://localhost:8080 + * should include the subfolder if owncloud runs in a subfolder + * e.g. http://localhost:8080/owncloud-core + * @param string|null $user + * @param string|null $password or token when bearer auth is used + * @param string|null $method PUT, GET, DELETE, etc. + * @param string|null $path + * @param array|null $headers + * @param string|null $xRequestId + * @param string|null|resource|StreamInterface $body + * @param int|null $davPathVersionToUse (1|2|3) + * @param string|null $type of request + * @param string|null $sourceIpAddress to initiate the request from + * @param string|null $authType basic|bearer + * @param bool $stream Set to true to stream a response rather + * than download it all up-front. + * @param int|null $timeout + * @param Client|null $client + * @param array|null $urlParameter to concatenate with path + * @param string|null $doDavRequestAsUser run the DAV as this user, if null its same as $user + * + * @return ResponseInterface + * @throws GuzzleException + * @throws Exception + */ + public static function makeDavRequest( + ?string $baseUrl, + ?string $user, + ?string $password, + ?string $method, + ?string $path, + ?array $headers, + ?string $xRequestId = '', + $body = null, + ?int $davPathVersionToUse = self::DAV_VERSION_OLD, + ?string $type = "files", + ?string $sourceIpAddress = null, + ?string $authType = "basic", + ?bool $stream = false, + ?int $timeout = 0, + ?Client $client = null, + ?array $urlParameter = [], + ?string $doDavRequestAsUser = null + ):ResponseInterface { + $baseUrl = self::sanitizeUrl($baseUrl, true); + + // We need to manipulate and use path as a string. + // So ensure that it is a string to avoid any type-conversion errors. + if ($path === null) { + $path = ""; + } + + // get space id if testing with spaces dav + if (self::$SPACE_ID_FROM_OCIS === '' && $davPathVersionToUse === self::DAV_VERSION_SPACES) { + $spaceId = self::getPersonalSpaceIdForUserOrFakeIfNotFound( + $baseUrl, + $doDavRequestAsUser ?? $user, + $password, + $xRequestId + ); + } else { + $spaceId = self::$SPACE_ID_FROM_OCIS; + } + + $davPath = self::getDavPath($doDavRequestAsUser ?? $user, $davPathVersionToUse, $type, $spaceId); + + //replace %, # and ? and in the path, Guzzle will not encode them + $urlSpecialChar = [['%', '#', '?'], ['%25', '%23', '%3F']]; + $path = \str_replace($urlSpecialChar[0], $urlSpecialChar[1], $path); + + if (!empty($urlParameter)) { + $urlParameter = \http_build_query($urlParameter, '', '&'); + $path .= '?' . $urlParameter; + } + $fullUrl = self::sanitizeUrl($baseUrl . $davPath . $path); + + if ($authType === 'bearer') { + $headers['Authorization'] = 'Bearer ' . $password; + $user = null; + $password = null; + } + if ($type === "public-files-new") { + if ($password === null || $password === "") { + $user = null; + } else { + $user = "public"; + } + } + $config = null; + if ($sourceIpAddress !== null) { + $config = [ 'curl' => [ CURLOPT_INTERFACE => $sourceIpAddress ]]; + } + + if ($headers !== null) { + foreach ($headers as $key => $value) { + //? and # need to be encoded in the Destination URL + if ($key === "Destination") { + $headers[$key] = \str_replace( + $urlSpecialChar[0], + $urlSpecialChar[1], + $value + ); + break; + } + } + } + + //Clear the space ID from ocis after each request + self::$SPACE_ID_FROM_OCIS = ''; + return HttpRequestHelper::sendRequest( + $fullUrl, + $xRequestId, + $method, + $user, + $password, + $headers, + $body, + $config, + null, + $stream, + $timeout, + $client + ); + } + + /** + * get the dav path + * + * @param string|null $user + * @param int|null $davPathVersionToUse (1|2) + * @param string|null $type + * @param string|null $spaceId + * + * @return string + */ + public static function getDavPath( + ?string $user, + ?int $davPathVersionToUse = null, + ?string $type = "files", + ?string $spaceId = null + ):string { + $newTrashbinDavPath = "remote.php/dav/trash-bin/$user/"; + if ($type === "public-files" || $type === "public-files-old") { + return "public.php/webdav/"; + } + if ($type === "public-files-new") { + return "remote.php/dav/public-files/$user/"; + } + if ($type === "archive") { + return "remote.php/dav/archive/$user/files"; + } + if ($type === "customgroups") { + return "remote.php/dav/"; + } + if ($davPathVersionToUse === self::DAV_VERSION_SPACES) { + if (($spaceId === null) || (\strlen($spaceId) === 0)) { + throw new InvalidArgumentException( + __METHOD__ . " A spaceId must be passed when using DAV path version 3 (spaces)" + ); + } + if ($type === "trash-bin") { + return "/remote.php/dav/spaces/trash-bin/" . $spaceId . '/'; + } + return "dav/spaces/" . $spaceId . '/'; + } else { + if ($davPathVersionToUse === self::DAV_VERSION_OLD) { + if ($type === "trash-bin") { + // Since there is no trash bin endpoint for old dav version, new dav version's endpoint is used here. + return $newTrashbinDavPath; + } + return "remote.php/webdav/"; + } elseif ($davPathVersionToUse === self::DAV_VERSION_NEW) { + if ($type === "files") { + $path = 'remote.php/dav/files/'; + return $path . $user . '/'; + } elseif ($type === "trash-bin") { + return $newTrashbinDavPath; + } else { + return "remote.php/dav"; + } + } else { + throw new InvalidArgumentException( + "DAV path version $davPathVersionToUse is unknown" + ); + } + } + } + + /** + * make sure there are no double slash in the URL + * + * @param string|null $url + * @param bool|null $trailingSlash forces a trailing slash + * + * @return string + */ + public static function sanitizeUrl(?string $url, ?bool $trailingSlash = false):string { + if ($trailingSlash === true) { + $url = $url . "/"; + } else { + $url = \rtrim($url, "/"); + } + $url = \preg_replace("/([^:]\/)\/+/", '$1', $url); + return $url; + } + + /** + * decides if the proposed dav version and chunking version are + * a valid combination. + * If no chunkingVersion is specified, then any dav version is valid. + * If a chunkingVersion is specified, then it has to match the dav version. + * Note: in future the dav and chunking versions might or might not + * move together and/or be supported together. So a more complex + * matrix could be needed here. + * + * @param string|int $davPathVersion + * @param string|int|null $chunkingVersion + * + * @return boolean is this a valid combination + */ + public static function isValidDavChunkingCombination( + $davPathVersion, + $chunkingVersion + ): bool { + if ($davPathVersion === self::DAV_VERSION_SPACES) { + // allow only old chunking version when using the spaces dav + return $chunkingVersion === 1; + } + return ( + ($chunkingVersion === 'no' || $chunkingVersion === null) || + ($davPathVersion === $chunkingVersion) + ); + } + + /** + * get Mtime of File in a public link share + * + * @param string|null $baseUrl + * @param string|null $fileName + * @param string|null $token + * @param string|null $xRequestId + * @param int|null $davVersionToUse + * + * @return string + * @throws Exception + */ + public static function getMtimeOfFileinPublicLinkShare( + ?string $baseUrl, + ?string $fileName, + ?string $token, + ?string $xRequestId = '', + ?int $davVersionToUse = self::DAV_VERSION_NEW + ):string { + $response = self::propfind( + $baseUrl, + null, + null, + "/public-files/{$token}/{$fileName}", + ['d:getlastmodified'], + $xRequestId, + '1', + null, + $davVersionToUse + ); + $responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $xmlPart = $responseXmlObject->xpath("//d:getlastmodified"); + + return $xmlPart[0]->__toString(); + } + + /** + * get Mtime of a resource + * + * @param string|null $user + * @param string|null $password + * @param string|null $baseUrl + * @param string|null $resource + * @param string|null $xRequestId + * @param int|null $davPathVersionToUse + * + * @return string + * @throws Exception + */ + public static function getMtimeOfResource( + ?string $user, + ?string $password, + ?string $baseUrl, + ?string $resource, + ?string $xRequestId = '', + ?int $davPathVersionToUse = self::DAV_VERSION_NEW + ):string { + $response = self::propfind( + $baseUrl, + $user, + $password, + $resource, + ["getlastmodified"], + $xRequestId, + "0", + "files", + $davPathVersionToUse + ); + $responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $xmlpart = $responseXmlObject->xpath("//d:getlastmodified"); + Assert::assertArrayHasKey( + 0, + $xmlpart, + __METHOD__ . " XML part does not have key 0. Expected a value at index 0 of 'xmlPart' but, found: " . (string) json_encode($xmlpart) + ); + $mtime = new DateTime($xmlpart[0]->__toString()); + return $mtime->format('U'); + } +} diff --git a/tests/acceptance/config/behat-core.yml b/tests/acceptance/config/behat-core.yml new file mode 100644 index 00000000000..dacc4c83742 --- /dev/null +++ b/tests/acceptance/config/behat-core.yml @@ -0,0 +1,457 @@ +default: + autoload: + "": "%paths.base%/../features/bootstrap" + suites: + coreApiMain: + paths: + - "%paths.base%/../features/coreApiMain" + context: &common_ldap_suite_context + parameters: + ldapAdminPassword: admin + ldapUsersOU: TestUsers + ldapGroupsOU: TestGroups + ldapInitialUserFilePath: /../../config/ldap-users.ldif + contexts: + - FeatureContext: &common_feature_context_params + baseUrl: http://localhost:8080 + adminUsername: admin + adminPassword: admin + regularUserPassword: 123456 + ocPath: apps/testing/api/v1/occ + - AppConfigurationContext: + - ChecksumContext: + - FilesVersionsContext: + - OccContext: + - TrashbinContext: + + coreApiAuth: + paths: + - "%paths.base%/../features/coreApiAuth" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - AuthContext: + + coreApiAuthOcs: + paths: + - "%paths.base%/../features/coreApiAuthOcs" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - AuthContext: + + coreApiAuthWebDav: + paths: + - "%paths.base%/../features/coreApiAuthWebDav" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - SearchContext: + - PublicWebDavContext: + - WebDavPropertiesContext: + - AuthContext: + + coreApiCapabilities: + paths: + - "%paths.base%/../features/coreApiCapabilities" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - CapabilitiesContext: + - OccContext: + - AppConfigurationContext: + + coreApiFavorites: + paths: + - "%paths.base%/../features/coreApiFavorites" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - FavoritesContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + - OccContext: + + coreApiShareCreateSpecialToShares1: + paths: + - "%paths.base%/../features/coreApiShareCreateSpecialToShares1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiShareCreateSpecialToShares2: + paths: + - "%paths.base%/../features/coreApiShareCreateSpecialToShares2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiSharees: + paths: + - "%paths.base%/../features/coreApiSharees" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - ShareesContext: + - OccContext: + - AppConfigurationContext: + + coreApiShareManagementToShares: + paths: + - "%paths.base%/../features/coreApiShareManagementToShares" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + - FilesVersionsContext: + + coreApiShareManagementBasicToShares: + paths: + - "%paths.base%/../features/coreApiShareManagementBasicToShares" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AuthContext: + + coreApiShareOperationsToShares1: + paths: + - "%paths.base%/../features/coreApiShareOperationsToShares1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + + coreApiShareOperationsToShares2: + paths: + - "%paths.base%/../features/coreApiShareOperationsToShares2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + + coreApiSharePublicLink1: + paths: + - "%paths.base%/../features/coreApiSharePublicLink1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiSharePublicLink2: + paths: + - "%paths.base%/../features/coreApiSharePublicLink2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiSharePublicLink3: + paths: + - "%paths.base%/../features/coreApiSharePublicLink3" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiShareReshareToShares1: + paths: + - "%paths.base%/../features/coreApiShareReshareToShares1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + + coreApiShareReshareToShares2: + paths: + - "%paths.base%/../features/coreApiShareReshareToShares2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiShareReshareToShares3: + paths: + - "%paths.base%/../features/coreApiShareReshareToShares3" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiShareUpdateToShares: + paths: + - "%paths.base%/../features/coreApiShareUpdateToShares" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TrashbinContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiTrashbin: + paths: + - "%paths.base%/../features/coreApiTrashbin" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - AppConfigurationContext: + - WebDavPropertiesContext: + + coreApiTrashbinRestore: + paths: + - "%paths.base%/../features/coreApiTrashbinRestore" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - AppConfigurationContext: + - WebDavPropertiesContext: + + coreApiVersions: + paths: + - "%paths.base%/../features/coreApiVersions" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - ChecksumContext: + - FilesVersionsContext: + - WebDavPropertiesContext: + - OccContext: + - AppConfigurationContext: + - TrashbinContext: + + coreApiWebdavDelete: + paths: + - "%paths.base%/../features/coreApiWebdavDelete" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - SearchContext: + - PublicWebDavContext: + - WebDavPropertiesContext: + - TrashbinContext: + + coreApiWebdavLocks: + paths: + - "%paths.base%/../features/coreApiWebdavLocks" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - WebDavLockingContext: + - WebDavPropertiesContext: + + coreApiWebdavLocks2: + paths: + - "%paths.base%/../features/coreApiWebdavLocks2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - WebDavLockingContext: + - WebDavPropertiesContext: + + coreApiWebdavLocks3: + paths: + - "%paths.base%/../features/coreApiWebdavLocks3" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - WebDavLockingContext: + - WebDavPropertiesContext: + + coreApiWebdavLocksUnlock: + paths: + - "%paths.base%/../features/coreApiWebdavLocksUnlock" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - WebDavLockingContext: + - WebDavPropertiesContext: + + coreApiWebdavMove1: + paths: + - "%paths.base%/../features/coreApiWebdavMove1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - WebDavPropertiesContext: + + coreApiWebdavMove2: + paths: + - "%paths.base%/../features/coreApiWebdavMove2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - WebDavPropertiesContext: + + coreApiWebdavOperations: + paths: + - "%paths.base%/../features/coreApiWebdavOperations" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - SearchContext: + - PublicWebDavContext: + - WebDavPropertiesContext: + - TagsContext: + - TrashbinContext: + + coreApiWebdavPreviews: + paths: + - "%paths.base%/../features/coreApiWebdavPreviews" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - WebDavPropertiesContext: + - OccContext: + + coreApiWebdavProperties1: + paths: + - "%paths.base%/../features/coreApiWebdavProperties1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiWebdavProperties2: + paths: + - "%paths.base%/../features/coreApiWebdavProperties2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiWebdavUpload1: + paths: + - "%paths.base%/../features/coreApiWebdavUpload1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - LoggingContext: + - OccContext: + - PublicWebDavContext: + - WebDavPropertiesContext: + + coreApiWebdavUpload2: + paths: + - "%paths.base%/../features/coreApiWebdavUpload2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - LoggingContext: + - OccContext: + - PublicWebDavContext: + + coreApiWebdavUploadTUS: + paths: + - "%paths.base%/../features/coreApiWebdavUploadTUS" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - PublicWebDavContext: + - TUSContext: + - FilesVersionsContext: + - ChecksumContext: + + coreApiWebdavEtagPropagation1: + paths: + - "%paths.base%/../features/coreApiWebdavEtagPropagation1" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - PublicWebDavContext: + - FilesVersionsContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiWebdavEtagPropagation2: + paths: + - "%paths.base%/../features/coreApiWebdavEtagPropagation2" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - OccContext: + - TrashbinContext: + - PublicWebDavContext: + - FilesVersionsContext: + - WebDavPropertiesContext: + - AppConfigurationContext: + + coreApiTranslation: + paths: + - "%paths.base%/../features/coreApiTranslation" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + + extensions: + rdx\behatvars\BehatVariablesExtension: ~ + + Cjm\Behat\StepThroughExtension: ~ diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 6527404c118..4c83ec68595 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -73,7 +73,7 @@ default: - WebDavPropertiesContext: - TUSContext: - SpacesTUSContext: - + apiContract: paths: - '%paths.base%/../features/apiContract' @@ -152,4 +152,6 @@ default: - TrashbinContext: extensions: + rdx\behatvars\BehatVariablesExtension: ~ + Cjm\Behat\StepThroughExtension: ~ diff --git a/tests/acceptance/config/ldap-users.ldif b/tests/acceptance/config/ldap-users.ldif new file mode 100644 index 00000000000..5216b135896 --- /dev/null +++ b/tests/acceptance/config/ldap-users.ldif @@ -0,0 +1,9 @@ +dn: ou=TestUsers,dc=owncloud,dc=com +objectclass: top +objectclass: organizationalUnit +ou: TestUsers + +dn: ou=TestGroups,dc=owncloud,dc=com +objectclass: top +objectclass: organizationalUnit +ou: TestGroups \ No newline at end of file diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md index d3c12c37365..624584bf778 100644 --- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md @@ -1,6 +1,6 @@ ## Scenarios from ownCloud10 core API tests that are expected to fail with OCIS storage while running with the Graph API -The expected failures in this file are from features in the owncloud/core repo. +The expected failures in this file are from features in the owncloud/ocis repo. ### File @@ -8,42 +8,42 @@ Basic file management like up and download, move, copy, properties, trash, versi #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) -- [apiWebdavProperties1/copyFile.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L274) -- [apiWebdavProperties1/copyFile.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L275) -- [apiWebdavProperties1/copyFile.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L292) -- [apiWebdavProperties1/copyFile.feature:293](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L293) +- [coreApiWebdavProperties1/copyFile.feature:274](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L274) +- [coreApiWebdavProperties1/copyFile.feature:275](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L275) +- [coreApiWebdavProperties1/copyFile.feature:292](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L292) +- [coreApiWebdavProperties1/copyFile.feature:293](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L293) #### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140) _ocdav: double-check the webdav property parsing when custom namespaces are used_ -- [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) -- [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) -- [apiWebdavProperties1/setFileProperties.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L43) -- [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) -- [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) -- [apiWebdavProperties1/setFileProperties.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L84) +- [coreApiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L37) +- [coreApiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L38) +- [coreApiWebdavProperties1/setFileProperties.feature:43](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L43) +- [coreApiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L78) +- [coreApiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L79) +- [coreApiWebdavProperties1/setFileProperties.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature#L84) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) -- [apiWebdavProperties2/getFileProperties.feature:360](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L360) -- [apiWebdavProperties2/getFileProperties.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L365) -- [apiWebdavProperties2/getFileProperties.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L370) -- [apiWebdavProperties2/getFileProperties.feature:400](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L400) -- [apiWebdavProperties2/getFileProperties.feature:405](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L405) -- [apiWebdavProperties2/getFileProperties.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L410) +- [coreApiWebdavProperties2/getFileProperties.feature:360](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L360) +- [coreApiWebdavProperties2/getFileProperties.feature:365](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L365) +- [coreApiWebdavProperties2/getFileProperties.feature:370](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L370) +- [coreApiWebdavProperties2/getFileProperties.feature:400](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L400) +- [coreApiWebdavProperties2/getFileProperties.feature:405](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L405) +- [coreApiWebdavProperties2/getFileProperties.feature:410](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature#L410) #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) -- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) -- [apiVersions/fileVersionAuthor.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L37) -- [apiVersions/fileVersionAuthor.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L58) -- [apiVersions/fileVersionAuthor.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L78) -- [apiVersions/fileVersionAuthor.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L104) -- [apiVersions/fileVersionAuthor.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L129) -- [apiVersions/fileVersionAuthor.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L154) -- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) -- [apiVersions/fileVersionAuthor.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L223) +- [coreApiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L14) +- [coreApiVersions/fileVersionAuthor.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L37) +- [coreApiVersions/fileVersionAuthor.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L58) +- [coreApiVersions/fileVersionAuthor.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L78) +- [coreApiVersions/fileVersionAuthor.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L104) +- [coreApiVersions/fileVersionAuthor.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L129) +- [coreApiVersions/fileVersionAuthor.feature:154](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L154) +- [coreApiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L180) +- [coreApiVersions/fileVersionAuthor.feature:223](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L223) ### Sync @@ -51,103 +51,103 @@ Synchronization features like etag propagation, setting mtime and locking files #### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) -- [apiMain/checksums.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L369) -- [apiMain/checksums.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L374) +- [coreApiMain/checksums.feature:369](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiMain/checksums.feature#L369) +- [coreApiMain/checksums.feature:374](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiMain/checksums.feature#L374) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) -- [apiWebdavLocks/exclusiveLocks.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L124) -- [apiWebdavLocks/exclusiveLocks.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L125) -- [apiWebdavLocks/exclusiveLocks.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L126) -- [apiWebdavLocks/exclusiveLocks.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L127) -- [apiWebdavLocks/exclusiveLocks.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L132) -- [apiWebdavLocks/exclusiveLocks.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L133) -- [apiWebdavLocks/exclusiveLocks.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L151) -- [apiWebdavLocks/exclusiveLocks.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L152) -- [apiWebdavLocks/exclusiveLocks.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L153) -- [apiWebdavLocks/exclusiveLocks.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L154) -- [apiWebdavLocks/exclusiveLocks.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L159) -- [apiWebdavLocks/exclusiveLocks.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L160) -- [apiWebdavLocks/exclusiveLocks.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L178) -- [apiWebdavLocks/exclusiveLocks.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L179) -- [apiWebdavLocks/exclusiveLocks.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L180) -- [apiWebdavLocks/exclusiveLocks.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L181) -- [apiWebdavLocks/exclusiveLocks.feature:186](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L186) -- [apiWebdavLocks/exclusiveLocks.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L187) -- [apiWebdavLocks/requestsWithToken.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L126) -- [apiWebdavLocks/requestsWithToken.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L127) -- [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) -- [apiWebdavLocks3/independentLocks.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L65) -- [apiWebdavLocks3/independentLocks.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L66) -- [apiWebdavLocks3/independentLocks.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L67) -- [apiWebdavLocks3/independentLocks.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L68) -- [apiWebdavLocks3/independentLocks.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L73) -- [apiWebdavLocks3/independentLocks.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L74) -- [apiWebdavLocks3/independentLocks.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L93) -- [apiWebdavLocks3/independentLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L94) -- [apiWebdavLocks3/independentLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L95) -- [apiWebdavLocks3/independentLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L96) -- [apiWebdavLocks3/independentLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L97) -- [apiWebdavLocks3/independentLocks.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L98) -- [apiWebdavLocks3/independentLocks.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L99) -- [apiWebdavLocks3/independentLocks.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L100) -- [apiWebdavLocks3/independentLocks.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L105) -- [apiWebdavLocks3/independentLocks.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L106) -- [apiWebdavLocks3/independentLocks.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L107) -- [apiWebdavLocks3/independentLocks.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L108) -- [apiWebdavLocks3/independentLocksShareToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L75) -- [apiWebdavLocks3/independentLocksShareToShares.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L76) -- [apiWebdavLocks3/independentLocksShareToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L77) -- [apiWebdavLocks3/independentLocksShareToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L78) -- [apiWebdavLocks3/independentLocksShareToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L83) -- [apiWebdavLocks3/independentLocksShareToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L84) -- [apiWebdavLocks3/independentLocksShareToShares.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L104) -- [apiWebdavLocks3/independentLocksShareToShares.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L105) -- [apiWebdavLocks3/independentLocksShareToShares.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L106) -- [apiWebdavLocks3/independentLocksShareToShares.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L107) -- [apiWebdavLocks3/independentLocksShareToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L112) -- [apiWebdavLocks3/independentLocksShareToShares.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L113) -- [apiWebdavLocksUnlock/unlock.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L40) -- [apiWebdavLocksUnlock/unlock.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L41) -- [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) -- [apiWebdavLocksUnlock/unlock.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L79) -- [apiWebdavLocksUnlock/unlock.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L80) -- [apiWebdavLocksUnlock/unlock.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L129) -- [apiWebdavLocksUnlock/unlock.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L130) -- [apiWebdavLocksUnlock/unlock.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L131) -- [apiWebdavLocksUnlock/unlock.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L132) -- [apiWebdavLocksUnlock/unlock.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L137) -- [apiWebdavLocksUnlock/unlock.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L138) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L27) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L28) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L29) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L30) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L35) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L36) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L51) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L52) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L53) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L54) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L59) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L100) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L101) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L102) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L107) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L108) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L123) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L124) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L125) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L126) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L131) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L132) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L147) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L148) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L149) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L150) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L155) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L156) +- [coreApiWebdavLocks/exclusiveLocks.feature:124](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L124) +- [coreApiWebdavLocks/exclusiveLocks.feature:125](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L125) +- [coreApiWebdavLocks/exclusiveLocks.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L126) +- [coreApiWebdavLocks/exclusiveLocks.feature:127](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L127) +- [coreApiWebdavLocks/exclusiveLocks.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L132) +- [coreApiWebdavLocks/exclusiveLocks.feature:133](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L133) +- [coreApiWebdavLocks/exclusiveLocks.feature:151](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L151) +- [coreApiWebdavLocks/exclusiveLocks.feature:152](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L152) +- [coreApiWebdavLocks/exclusiveLocks.feature:153](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L153) +- [coreApiWebdavLocks/exclusiveLocks.feature:154](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L154) +- [coreApiWebdavLocks/exclusiveLocks.feature:159](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L159) +- [coreApiWebdavLocks/exclusiveLocks.feature:160](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L160) +- [coreApiWebdavLocks/exclusiveLocks.feature:178](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L178) +- [coreApiWebdavLocks/exclusiveLocks.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L179) +- [coreApiWebdavLocks/exclusiveLocks.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L180) +- [coreApiWebdavLocks/exclusiveLocks.feature:181](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L181) +- [coreApiWebdavLocks/exclusiveLocks.feature:186](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L186) +- [coreApiWebdavLocks/exclusiveLocks.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature#L187) +- [coreApiWebdavLocks/requestsWithToken.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L126) +- [coreApiWebdavLocks/requestsWithToken.feature:127](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L127) +- [coreApiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature#L132) +- [coreApiWebdavLocks3/independentLocks.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L65) +- [coreApiWebdavLocks3/independentLocks.feature:66](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L66) +- [coreApiWebdavLocks3/independentLocks.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L67) +- [coreApiWebdavLocks3/independentLocks.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L68) +- [coreApiWebdavLocks3/independentLocks.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L73) +- [coreApiWebdavLocks3/independentLocks.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L74) +- [coreApiWebdavLocks3/independentLocks.feature:93](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L93) +- [coreApiWebdavLocks3/independentLocks.feature:94](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L94) +- [coreApiWebdavLocks3/independentLocks.feature:95](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L95) +- [coreApiWebdavLocks3/independentLocks.feature:96](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L96) +- [coreApiWebdavLocks3/independentLocks.feature:97](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L97) +- [coreApiWebdavLocks3/independentLocks.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L98) +- [coreApiWebdavLocks3/independentLocks.feature:99](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L99) +- [coreApiWebdavLocks3/independentLocks.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L100) +- [coreApiWebdavLocks3/independentLocks.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L105) +- [coreApiWebdavLocks3/independentLocks.feature:106](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L106) +- [coreApiWebdavLocks3/independentLocks.feature:107](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L107) +- [coreApiWebdavLocks3/independentLocks.feature:108](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature#L108) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:75](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L75) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L76) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:77](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L77) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L78) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:83](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L83) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L84) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L104) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L105) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:106](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L106) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:107](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L107) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:112](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L112) +- [coreApiWebdavLocks3/independentLocksShareToShares.feature:113](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature#L113) +- [coreApiWebdavLocksUnlock/unlock.feature:40](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L40) +- [coreApiWebdavLocksUnlock/unlock.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L41) +- [coreApiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L46) +- [coreApiWebdavLocksUnlock/unlock.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L79) +- [coreApiWebdavLocksUnlock/unlock.feature:80](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L80) +- [coreApiWebdavLocksUnlock/unlock.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L129) +- [coreApiWebdavLocksUnlock/unlock.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L130) +- [coreApiWebdavLocksUnlock/unlock.feature:131](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L131) +- [coreApiWebdavLocksUnlock/unlock.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L132) +- [coreApiWebdavLocksUnlock/unlock.feature:137](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L137) +- [coreApiWebdavLocksUnlock/unlock.feature:138](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature#L138) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L27) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L28) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L29) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L30) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L35) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L36) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L51) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L52) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L53) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L54) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:59](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L59) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L60) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L99) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L100) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L101) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:102](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L102) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:107](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L107) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:108](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L108) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:123](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L123) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:124](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L124) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:125](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L125) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L126) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:131](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L131) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L132) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:147](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L147) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:148](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L148) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:149](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L149) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:150](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L150) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:155](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L155) +- [coreApiWebdavLocksUnlock/unlockSharingToShares.feature:156](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature#L156) ### Share @@ -155,327 +155,327 @@ File and sync features in a shared scenario #### [ocs sharing api always returns an empty exact list while searching for a sharee](https://github.com/owncloud/ocis/issues/4265) -- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) -- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) -- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) -- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) -- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) -- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) -- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) -- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) -- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) -- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) +- [coreApiSharees/sharees.feature:350](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L350) +- [coreApiSharees/sharees.feature:351](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L351) +- [coreApiSharees/sharees.feature:370](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L370) +- [coreApiSharees/sharees.feature:371](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L371) +- [coreApiSharees/sharees.feature:390](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L390) +- [coreApiSharees/sharees.feature:391](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L391) +- [coreApiSharees/sharees.feature:410](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L410) +- [coreApiSharees/sharees.feature:411](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L411) +- [coreApiSharees/sharees.feature:430](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L430) +- [coreApiSharees/sharees.feature:431](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharees/sharees.feature#L431) #### [accepting matching name shared resources from different users/groups sets no serial identifiers on the resource name for the receiver](https://github.com/owncloud/ocis/issues/4289) -- [apiShareManagementToShares/acceptShares.feature:334](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L334) -- [apiShareManagementToShares/acceptShares.feature:364](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L364) -- [apiShareManagementToShares/acceptShares.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L278) -- [apiShareManagementToShares/acceptShares.feature:543](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L543) -- [apiShareManagementToShares/acceptShares.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L608) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L141) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L142) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L181) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L182) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L45) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L46) +- [coreApiShareManagementToShares/acceptShares.feature:334](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L334) +- [coreApiShareManagementToShares/acceptShares.feature:364](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L364) +- [coreApiShareManagementToShares/acceptShares.feature:278](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L278) +- [coreApiShareManagementToShares/acceptShares.feature:543](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L543) +- [coreApiShareManagementToShares/acceptShares.feature:608](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L608) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:141](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L141) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:142](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L142) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:181](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L181) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:182](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L182) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:45](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L45) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L46) #### [sharing the shares folder to users exits with different status code than in oc10 backend](https://github.com/owncloud/ocis/issues/2215) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:747](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L747) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:748](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L748) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L766) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:767](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L767) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:782](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L782) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:783](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L783) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:747](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L747) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:748](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L748) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:766](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L766) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:767](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L767) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:782](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L782) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:783](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L783) #### [file_target of an auto-renamed file is not correct directly after sharing](https://github.com/owncloud/core/issues/32322) -- [apiShareManagementToShares/mergeShare.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L121) +- [coreApiShareManagementToShares/mergeShare.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature#L121) #### [File deletion using dav gives unique string in filename in the trashbin](https://github.com/owncloud/product/issues/178) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L63) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L77) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L63) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:77](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L77) cannot share a folder with create permission #### [Resource with share permission create is readable for sharee](https://github.com/owncloud/ocis/issues/4524) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L130) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L143) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L130) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:143](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L143) #### [OCS error message for attempting to access share via share id as an unauthorized user is not informative](https://github.com/owncloud/ocis/issues/1233) -- [apiShareOperationsToShares1/gettingShares.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L184) -- [apiShareOperationsToShares1/gettingShares.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L185) +- [coreApiShareOperationsToShares1/gettingShares.feature:184](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L184) +- [coreApiShareOperationsToShares1/gettingShares.feature:185](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L185) #### [Listing shares via ocs API does not show path for parent folders](https://github.com/owncloud/ocis/issues/1231) -- [apiShareOperationsToShares1/gettingShares.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L221) -- [apiShareOperationsToShares1/gettingShares.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L222) +- [coreApiShareOperationsToShares1/gettingShares.feature:221](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L221) +- [coreApiShareOperationsToShares1/gettingShares.feature:222](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L222) #### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L10) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L21) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L31) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L47) -- [apiSharePublicLink1/createPublicLinkShare.feature:528](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L528) -- [apiSharePublicLink1/createPublicLinkShare.feature:549](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L549) +- [coreApiSharePublicLink1/accessToPublicLinkShare.feature:10](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature#L10) +- [coreApiSharePublicLink1/accessToPublicLinkShare.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature#L21) +- [coreApiSharePublicLink1/accessToPublicLinkShare.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature#L31) +- [coreApiSharePublicLink1/accessToPublicLinkShare.feature:47](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature#L47) +- [coreApiSharePublicLink1/createPublicLinkShare.feature:528](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L528) +- [coreApiSharePublicLink1/createPublicLinkShare.feature:549](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L549) #### [download previews of other users file](https://github.com/owncloud/ocis/issues/2071) -- [apiWebdavPreviews/previews.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L102) +- [coreApiWebdavPreviews/previews.feature:102](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L102) #### [different error message detail for previews of folder](https://github.com/owncloud/ocis/issues/2064) -- [apiWebdavPreviews/previews.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L111) +- [coreApiWebdavPreviews/previews.feature:111](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L111) #### [Requesting a file preview when it is disabled by the administrator](https://github.com/owncloud/ocis/issues/192) -- [apiWebdavPreviews/previews.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L126) +- [coreApiWebdavPreviews/previews.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L126) #### [Cannot set/unset maximum and minimum preview dimensions](https://github.com/owncloud/ocis/issues/2070) -- [apiWebdavPreviews/previews.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L134) -- [apiWebdavPreviews/previews.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L162) -- [apiWebdavPreviews/previews.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L163) -- [apiWebdavPreviews/previews.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L164) -- [apiWebdavPreviews/previews.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L176) -- [apiWebdavPreviews/previews.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L177) +- [coreApiWebdavPreviews/previews.feature:134](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L134) +- [coreApiWebdavPreviews/previews.feature:162](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L162) +- [coreApiWebdavPreviews/previews.feature:163](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L163) +- [coreApiWebdavPreviews/previews.feature:164](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L164) +- [coreApiWebdavPreviews/previews.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L176) +- [coreApiWebdavPreviews/previews.feature:177](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L177) #### [creating public links with permissions fails](https://github.com/owncloud/product/issues/252) -- [apiSharePublicLink1/changingPublicLinkShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L30) -- [apiSharePublicLink1/changingPublicLinkShare.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L51) -- [apiSharePublicLink1/changingPublicLinkShare.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L90) +- [coreApiSharePublicLink1/changingPublicLinkShare.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L30) +- [coreApiSharePublicLink1/changingPublicLinkShare.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L51) +- [coreApiSharePublicLink1/changingPublicLinkShare.feature:90](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L90) #### [copying a folder within a public link folder to folder with same name as an already existing file overwrites the parent file](https://github.com/owncloud/ocis/issues/1232) -- [apiSharePublicLink2/copyFromPublicLink.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L63) -- [apiSharePublicLink2/copyFromPublicLink.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L89) -- [apiSharePublicLink2/copyFromPublicLink.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L173) -- [apiSharePublicLink2/copyFromPublicLink.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L174) -- [apiSharePublicLink2/copyFromPublicLink.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L189) -- [apiSharePublicLink2/copyFromPublicLink.feature:190](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L190) -- [apiSharePublicLink3/updatePublicLinkShare.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/updatePublicLinkShare.feature#L45) -- [apiSharePublicLink3/updatePublicLinkShare.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/updatePublicLinkShare.feature#L46) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L63) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:89](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L89) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:173](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L173) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:174](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L174) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:189](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L189) +- [coreApiSharePublicLink2/copyFromPublicLink.feature:190](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L190) +- [coreApiSharePublicLink3/updatePublicLinkShare.feature:45](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature#L45) +- [coreApiSharePublicLink3/updatePublicLinkShare.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature#L46) #### [Upload-only shares must not overwrite but create a separate file](https://github.com/owncloud/ocis/issues/1267) -- [apiSharePublicLink3/uploadToPublicLinkShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/uploadToPublicLinkShare.feature#L24) -- [apiSharePublicLink3/uploadToPublicLinkShare.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/uploadToPublicLinkShare.feature#L273) +- [coreApiSharePublicLink3/uploadToPublicLinkShare.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature#L24) +- [coreApiSharePublicLink3/uploadToPublicLinkShare.feature:273](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature#L273) #### [Set quota over settings](https://github.com/owncloud/ocis/issues/1290) -- [apiSharePublicLink3/uploadToPublicLinkShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/uploadToPublicLinkShare.feature#L160) -- [apiSharePublicLink3/uploadToPublicLinkShare.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/uploadToPublicLinkShare.feature#L179) +- [coreApiSharePublicLink3/uploadToPublicLinkShare.feature:160](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature#L160) +- [coreApiSharePublicLink3/uploadToPublicLinkShare.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature#L179) #### [deleting a file inside a received shared folder is moved to the trash-bin of the sharer not the receiver](https://github.com/owncloud/ocis/issues/1124) -- [apiTrashbin/trashbinSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L29) -- [apiTrashbin/trashbinSharingToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L46) -- [apiTrashbin/trashbinSharingToShares.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L51) -- [apiTrashbin/trashbinSharingToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L73) -- [apiTrashbin/trashbinSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L78) -- [apiTrashbin/trashbinSharingToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L100) -- [apiTrashbin/trashbinSharingToShares.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L105) -- [apiTrashbin/trashbinSharingToShares.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L128) -- [apiTrashbin/trashbinSharingToShares.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L133) -- [apiTrashbin/trashbinSharingToShares.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L156) -- [apiTrashbin/trashbinSharingToShares.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L161) -- [apiTrashbin/trashbinSharingToShares.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L184) -- [apiTrashbin/trashbinSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L189) -- [apiTrashbin/trashbinSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L212) -- [apiTrashbin/trashbinSharingToShares.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L236) +- [coreApiTrashbin/trashbinSharingToShares.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L29) +- [coreApiTrashbin/trashbinSharingToShares.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L46) +- [coreApiTrashbin/trashbinSharingToShares.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L51) +- [coreApiTrashbin/trashbinSharingToShares.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L73) +- [coreApiTrashbin/trashbinSharingToShares.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L78) +- [coreApiTrashbin/trashbinSharingToShares.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L100) +- [coreApiTrashbin/trashbinSharingToShares.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L105) +- [coreApiTrashbin/trashbinSharingToShares.feature:128](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L128) +- [coreApiTrashbin/trashbinSharingToShares.feature:133](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L133) +- [coreApiTrashbin/trashbinSharingToShares.feature:156](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L156) +- [coreApiTrashbin/trashbinSharingToShares.feature:161](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L161) +- [coreApiTrashbin/trashbinSharingToShares.feature:184](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L184) +- [coreApiTrashbin/trashbinSharingToShares.feature:189](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L189) +- [coreApiTrashbin/trashbinSharingToShares.feature:212](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L212) +- [coreApiTrashbin/trashbinSharingToShares.feature:236](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L236) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) -- [apiShareOperationsToShares2/uploadToShare.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L210) -- [apiShareOperationsToShares2/uploadToShare.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L211) +- [coreApiShareOperationsToShares2/uploadToShare.feature:210](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature#L210) +- [coreApiShareOperationsToShares2/uploadToShare.feature:211](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature#L211) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) -- [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L26) -- [apiShareOperationsToShares1/changingFilesShare.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L109) -- [apiShareOperationsToShares1/changingFilesShare.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L110) -- [apiShareOperationsToShares1/changingFilesShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L131) -- [apiShareOperationsToShares1/changingFilesShare.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L132) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:538](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L538) -- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) -- [apiVersions/fileVersionsSharingToShares.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L221) -- [apiWebdavMove2/moveShareOnOcis.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L30) -- [apiWebdavMove2/moveShareOnOcis.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L32) -- [apiWebdavMove2/moveShareOnOcis.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L98) -- [apiWebdavMove2/moveShareOnOcis.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L100) -- [apiWebdavMove2/moveShareOnOcis.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L169) -- [apiWebdavMove2/moveShareOnOcis.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L170) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L25) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L26) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:109](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L109) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:110](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L110) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:131](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L131) +- [coreApiShareOperationsToShares1/changingFilesShare.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L132) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:538](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L538) +- [coreApiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L220) +- [coreApiVersions/fileVersionsSharingToShares.feature:221](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L221) +- [coreApiWebdavMove2/moveShareOnOcis.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L30) +- [coreApiWebdavMove2/moveShareOnOcis.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L32) +- [coreApiWebdavMove2/moveShareOnOcis.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L98) +- [coreApiWebdavMove2/moveShareOnOcis.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L100) +- [coreApiWebdavMove2/moveShareOnOcis.feature:169](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L169) +- [coreApiWebdavMove2/moveShareOnOcis.feature:170](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L170) #### [Expiration date for shares is not implemented](https://github.com/owncloud/ocis/issues/1250) #### Expiration date of user shares -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L52) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L53) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L76) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L77) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L102) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L103) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L128) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L129) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L279) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:280](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L280) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L301) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:302](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L302) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:323](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L323) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L324) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L346) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L347) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L363) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:364](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L364) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:380](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L380) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L381) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L576) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:577](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L577) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L599) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:600](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L600) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:601](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L601) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:602](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L602) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:603](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L603) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:624](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L624) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:625](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L625) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:626](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L626) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:627](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L627) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:628](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L628) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:629](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L629) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:630](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L630) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:631](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L631) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:632](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L632) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:633](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L633) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:634](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L634) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:635](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L635) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:656](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L656) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:657](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L657) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L658) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:659](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L659) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:660](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L660) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:661](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L661) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:682](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L682) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:683](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L683) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:684](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L684) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:685](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L685) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:686](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L686) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:687](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L687) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:708](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L708) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:709](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L709) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:732](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L732) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:733](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L733) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:756](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L756) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:757](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L757) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L34) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L35) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L86) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L87) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L143) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L144) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L201) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L202) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L203) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L204) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L287) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L288) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L318) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:319](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L319) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:320](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L320) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L321) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:379](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L379) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:380](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L380) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L381) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L382) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:383](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L383) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:384](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L384) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:413](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L413) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:414](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L414) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:415](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L415) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:416](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L416) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:444](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L444) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:445](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L445) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L52) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L53) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L76) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:77](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L77) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:102](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L102) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L103) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:128](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L128) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L129) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:279](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L279) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:280](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L280) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:301](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L301) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:302](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L302) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:323](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L323) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:324](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L324) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:346](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L346) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:347](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L347) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:363](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L363) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:364](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L364) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:380](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L380) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:381](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L381) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:576](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L576) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:577](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L577) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:599](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L599) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:600](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L600) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:601](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L601) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:602](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L602) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:603](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L603) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:624](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L624) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:625](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L625) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:626](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L626) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:627](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L627) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:628](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L628) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:629](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L629) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:630](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L630) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:631](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L631) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:632](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L632) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:633](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L633) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:634](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L634) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:635](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L635) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:656](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L656) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:657](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L657) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:658](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L658) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:659](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L659) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:660](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L660) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:661](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L661) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:682](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L682) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:683](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L683) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:684](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L684) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:685](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L685) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:686](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L686) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:687](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L687) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:708](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L708) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:709](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L709) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:732](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L732) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:733](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L733) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:756](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L756) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:757](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L757) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:34](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L34) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L35) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L86) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:87](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L87) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:143](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L143) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:144](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L144) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:201](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L201) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:202](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L202) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:203](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L203) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:204](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L204) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:287](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L287) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:288](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L288) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:318](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L318) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:319](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L319) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:320](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L320) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:321](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L321) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:379](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L379) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:380](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L380) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:381](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L381) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:382](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L382) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:383](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L383) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:384](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L384) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:413](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L413) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:414](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L414) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:415](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L415) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:416](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L416) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:444](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L444) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:445](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L445) #### Expiration date of group shares -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L175) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L176) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L201) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L202) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L229) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L230) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:258](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L258) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:259](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L259) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L403) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:404](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L404) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L427) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:428](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L428) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:451](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L451) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:452](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L452) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:476](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L476) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:477](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L477) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:497](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L497) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:498](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L498) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:518](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L518) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:519](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L519) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L60) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L61) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L116) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L117) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L172) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L173) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L232) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L233) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:234](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L234) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L235) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:175](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L175) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L176) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:201](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L201) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:202](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L202) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:229](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L229) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:230](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L230) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:258](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L258) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:259](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L259) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:403](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L403) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:404](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L404) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:427](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L427) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:428](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L428) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:451](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L451) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:452](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L452) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:476](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L476) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:477](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L477) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:497](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L497) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:498](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L498) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:518](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L518) +- [coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature:519](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature#L519) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L60) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:61](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L61) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:116](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L116) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:117](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L117) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:172](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L172) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:173](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L173) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:232](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L232) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:233](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L233) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:234](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L234) +- [coreApiShareReshareToShares3/reShareWithExpiryDate.feature:235](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature#L235) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) -- [apiShareUpdateToShares/updateShare.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L235) -- [apiShareUpdateToShares/updateShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L194) -- [apiShareManagementToShares/mergeShare.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L141) +- [coreApiShareUpdateToShares/updateShare.feature:235](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature#L235) +- [coreApiShareUpdateToShares/updateShare.feature:194](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature#L194) +- [coreApiShareManagementToShares/mergeShare.feature:141](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature#L141) #### [Sharing folder and sub-folder with same user but different permission,the permission of sub-folder is not obeyed ](https://github.com/owncloud/ocis/issues/2440) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L278) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:313](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L313) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:422](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L422) -- [apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:457](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L457) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:278](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L278) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:313](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L313) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:422](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L422) +- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:457](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L457) #### [Empty OCS response for a share create request using a disabled user](https://github.com/owncloud/ocis/issues/2212) -- [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L20) -- [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L23) +- [coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L20) +- [coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L23) #### [Edit user share response has a "name" field](https://github.com/owncloud/ocis/issues/1225) -- [apiShareUpdateToShares/updateShare.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L281) -- [apiShareUpdateToShares/updateShare.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L282) +- [coreApiShareUpdateToShares/updateShare.feature:281](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature#L281) +- [coreApiShareUpdateToShares/updateShare.feature:282](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature#L282) #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) -- [apiVersions/fileVersionsSharingToShares.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L283) +- [coreApiVersions/fileVersionsSharingToShares.feature:283](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L283) #### [Share lists deleted user as 'user'](https://github.com/owncloud/ocis/issues/903) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:682](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L682) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:683](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L683) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:682](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L682) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:683](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L683) #### [deleting a share with wrong authentication returns OCS status 996 / HTTP 500](https://github.com/owncloud/ocis/issues/1229) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L226) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L227) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:226](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L226) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:227](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L227) ### User Management @@ -485,14 +485,14 @@ User and group management features _ocs: api compatibility, return correct status code_ -- [apiShareOperationsToShares2/shareAccessByID.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L48) -- [apiShareOperationsToShares2/shareAccessByID.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L49) -- [apiShareOperationsToShares2/shareAccessByID.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L50) -- [apiShareOperationsToShares2/shareAccessByID.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L51) -- [apiShareOperationsToShares2/shareAccessByID.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L52) -- [apiShareOperationsToShares2/shareAccessByID.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L53) -- [apiShareOperationsToShares2/shareAccessByID.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L54) -- [apiShareOperationsToShares2/shareAccessByID.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L55) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:48](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L48) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L49) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:50](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L50) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L51) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L52) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L53) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L54) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L55) ### Other @@ -500,204 +500,204 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) -- [apiAuthOcs/ocsDELETEAuth.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L8) -- [apiAuthOcs/ocsGETAuth.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L8) -- [apiAuthOcs/ocsGETAuth.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L42) -- [apiAuthOcs/ocsGETAuth.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L73) -- [apiAuthOcs/ocsGETAuth.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L104) -- [apiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L121) -- [apiAuthOcs/ocsPOSTAuth.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPOSTAuth.feature#L8) -- [apiAuthOcs/ocsPUTAuth.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPUTAuth.feature#L8) +- [coreApiAuthOcs/ocsDELETEAuth.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsDELETEAuth.feature#L8) +- [coreApiAuthOcs/ocsGETAuth.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature#L8) +- [coreApiAuthOcs/ocsGETAuth.feature:42](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature#L42) +- [coreApiAuthOcs/ocsGETAuth.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature#L73) +- [coreApiAuthOcs/ocsGETAuth.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature#L104) +- [coreApiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature#L121) +- [coreApiAuthOcs/ocsPOSTAuth.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsPOSTAuth.feature#L8) +- [coreApiAuthOcs/ocsPUTAuth.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsPUTAuth.feature#L8) #### [sending MKCOL requests to another user's webDav endpoints as normal user gives 404 instead of 403 ](https://github.com/owncloud/ocis/issues/3872) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMKCOLAuth.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L52) -- [apiAuthWebDav/webDavMKCOLAuth.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L63) +- [coreApiAuthWebDav/webDavMKCOLAuth.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavMKCOLAuth.feature#L52) +- [coreApiAuthWebDav/webDavMKCOLAuth.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavMKCOLAuth.feature#L63) #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) -- [apiAuthWebDav/webDavLOCKAuth.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L56) -- [apiAuthWebDav/webDavLOCKAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L68) +- [coreApiAuthWebDav/webDavLOCKAuth.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavLOCKAuth.feature#L56) +- [coreApiAuthWebDav/webDavLOCKAuth.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavLOCKAuth.feature#L68) #### [send (MOVE,COPY) requests to another user's webDav endpoints as normal user gives 400 instead of 403](https://github.com/owncloud/ocis/issues/3882) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMOVEAuth.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L55) -- [apiAuthWebDav/webDavMOVEAuth.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L64) -- [apiAuthWebDav/webDavCOPYAuth.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavCOPYAuth.feature#L59) -- [apiAuthWebDav/webDavCOPYAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavCOPYAuth.feature#L68) +- [coreApiAuthWebDav/webDavMOVEAuth.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavMOVEAuth.feature#L55) +- [coreApiAuthWebDav/webDavMOVEAuth.feature:64](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavMOVEAuth.feature#L64) +- [coreApiAuthWebDav/webDavCOPYAuth.feature:59](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavCOPYAuth.feature#L59) +- [coreApiAuthWebDav/webDavCOPYAuth.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavCOPYAuth.feature#L68) #### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavPOSTAuth.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L56) -- [apiAuthWebDav/webDavPOSTAuth.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L65) +- [coreApiAuthWebDav/webDavPOSTAuth.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavPOSTAuth.feature#L56) +- [coreApiAuthWebDav/webDavPOSTAuth.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavPOSTAuth.feature#L65) #### Another users space literally does not exist because it is not listed as a space for him, 404 seems correct, expects 403 -- [apiAuthWebDav/webDavPUTAuth.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L56) -- [apiAuthWebDav/webDavPUTAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L68) +- [coreApiAuthWebDav/webDavPUTAuth.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavPUTAuth.feature#L56) +- [coreApiAuthWebDav/webDavPUTAuth.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavPUTAuth.feature#L68) #### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) -- [apiAuthWebDav/webDavSpecialURLs.feature:13](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L13) -- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) -- [apiAuthWebDav/webDavSpecialURLs.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L55) -- [apiAuthWebDav/webDavSpecialURLs.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L66) -- [apiAuthWebDav/webDavSpecialURLs.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L76) -- [apiAuthWebDav/webDavSpecialURLs.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L88) -- [apiAuthWebDav/webDavSpecialURLs.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L100) -- [apiAuthWebDav/webDavSpecialURLs.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L111) -- [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) -- [apiAuthWebDav/webDavSpecialURLs.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L132) -- [apiAuthWebDav/webDavSpecialURLs.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L142) -- [apiAuthWebDav/webDavSpecialURLs.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L153) -- [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) -- [apiAuthWebDav/webDavSpecialURLs.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L174) -- [apiAuthWebDav/webDavSpecialURLs.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L184) -- [apiAuthWebDav/webDavSpecialURLs.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L195) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L13) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L24) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L55) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:66](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L66) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L76) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:88](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L88) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L100) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:111](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L111) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L121) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L132) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:142](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L142) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:153](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L153) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L163) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:174](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L174) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:184](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L184) +- [coreApiAuthWebDav/webDavSpecialURLs.feature:195](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature#L195) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) -- [apiCapabilities/capabilitiesWithNormalUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilitiesWithNormalUser.feature#L11) +- [coreApiCapabilities/capabilitiesWithNormalUser.feature:11](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiCapabilities/capabilitiesWithNormalUser.feature#L11) #### [[old/new/spaces] In ocis and oc10, REPORT request response differently](https://github.com/owncloud/ocis/issues/4712) -- [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) -- [apiWebdavOperations/search.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L209) -- [apiWebdavOperations/search.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L214) -- [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) -- [apiWebdavOperations/search.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L241) -- [apiWebdavOperations/search.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L246) +- [coreApiWebdavOperations/search.feature:208](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L208) +- [coreApiWebdavOperations/search.feature:209](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L209) +- [coreApiWebdavOperations/search.feature:214](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L214) +- [coreApiWebdavOperations/search.feature:240](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L240) +- [coreApiWebdavOperations/search.feature:241](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L241) +- [coreApiWebdavOperations/search.feature:246](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L246) #### [Support for favorites](https://github.com/owncloud/ocis/issues/1228) -- [apiFavorites/favorites.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L115) -- [apiFavorites/favorites.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L116) -- [apiFavorites/favorites.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L142) -- [apiFavorites/favorites.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L143) -- [apiFavorites/favorites.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L264) -- [apiFavorites/favorites.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L265) +- [coreApiFavorites/favorites.feature:115](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L115) +- [coreApiFavorites/favorites.feature:116](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L116) +- [coreApiFavorites/favorites.feature:142](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L142) +- [coreApiFavorites/favorites.feature:143](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L143) +- [coreApiFavorites/favorites.feature:264](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L264) +- [coreApiFavorites/favorites.feature:265](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L265) And other missing implementation of favorites -- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) -- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) -- [apiFavorites/favorites.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L189) -- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L222) -- [apiFavorites/favoritesSharingToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L67) -- [apiFavorites/favoritesSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L68) +- [coreApiFavorites/favorites.feature:183](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L183) +- [coreApiFavorites/favorites.feature:184](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L184) +- [coreApiFavorites/favorites.feature:189](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L189) +- [coreApiFavorites/favorites.feature:216](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L216) +- [coreApiFavorites/favorites.feature:217](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L217) +- [coreApiFavorites/favorites.feature:222](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L222) +- [coreApiFavorites/favoritesSharingToShares.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favoritesSharingToShares.feature#L67) +- [coreApiFavorites/favoritesSharingToShares.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favoritesSharingToShares.feature#L68) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) -- [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) -- [apiWebdavOperations/refuseAccess.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L23) +- [coreApiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L22) +- [coreApiWebdavOperations/refuseAccess.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L23) #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) -- [apiAuthWebDav/webDavDELETEAuth.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L135) -- [apiAuthWebDav/webDavDELETEAuth.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L149) -- [apiAuthWebDav/webDavDELETEAuth.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L161) -- [apiAuthWebDav/webDavDELETEAuth.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L175) +- [coreApiAuthWebDav/webDavDELETEAuth.feature:135](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature#L135) +- [coreApiAuthWebDav/webDavDELETEAuth.feature:149](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature#L149) +- [coreApiAuthWebDav/webDavDELETEAuth.feature:161](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature#L161) +- [coreApiAuthWebDav/webDavDELETEAuth.feature:175](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature#L175) #### [Request to edit non-existing user by authorized admin gets unauthorized in http response](https://github.com/owncloud/core/issues/38423) -- [apiAuthOcs/ocsPUTAuth.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPUTAuth.feature#L24) +- [coreApiAuthOcs/ocsPUTAuth.feature:24](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiAuthOcs/ocsPUTAuth.feature#L24) #### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:730](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L730) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:731](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L731) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:730](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L730) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:731](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L731) #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) -- [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) -- [apiWebdavUploadTUS/checksums.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L85) -- [apiWebdavUploadTUS/checksums.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L86) -- [apiWebdavUploadTUS/checksums.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L87) -- [apiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L92) -- [apiWebdavUploadTUS/checksums.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L93) -- [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L174) -- [apiWebdavUploadTUS/checksums.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L179) -- [apiWebdavUploadTUS/checksums.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L226) -- [apiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L227) -- [apiWebdavUploadTUS/checksums.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L228) -- [apiWebdavUploadTUS/checksums.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L229) -- [apiWebdavUploadTUS/checksums.feature:234](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L234) -- [apiWebdavUploadTUS/checksums.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L235) -- [apiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L282) -- [apiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L283) -- [apiWebdavUploadTUS/checksums.feature:284](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L284) -- [apiWebdavUploadTUS/checksums.feature:285](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L285) -- [apiWebdavUploadTUS/checksums.feature:290](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L290) -- [apiWebdavUploadTUS/checksums.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L291) -- [apiWebdavUploadTUS/optionsRequest.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L8) -- [apiWebdavUploadTUS/optionsRequest.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L22) -- [apiWebdavUploadTUS/optionsRequest.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L35) -- [apiWebdavUploadTUS/optionsRequest.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L49) -- [apiWebdavUploadTUS/uploadToShare.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L176) -- [apiWebdavUploadTUS/uploadToShare.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L177) -- [apiWebdavUploadTUS/uploadToShare.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L195) -- [apiWebdavUploadTUS/uploadToShare.feature:196](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L196) -- [apiWebdavUploadTUS/uploadToShare.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L214) -- [apiWebdavUploadTUS/uploadToShare.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L215) -- [apiWebdavUploadTUS/uploadToShare.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L253) -- [apiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L254) -- [apiWebdavUploadTUS/uploadToShare.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L295) -- [apiWebdavUploadTUS/uploadToShare.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L296) +- [coreApiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L84) +- [coreApiWebdavUploadTUS/checksums.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L85) +- [coreApiWebdavUploadTUS/checksums.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L86) +- [coreApiWebdavUploadTUS/checksums.feature:87](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L87) +- [coreApiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L92) +- [coreApiWebdavUploadTUS/checksums.feature:93](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L93) +- [coreApiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L173) +- [coreApiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L174) +- [coreApiWebdavUploadTUS/checksums.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L179) +- [coreApiWebdavUploadTUS/checksums.feature:226](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L226) +- [coreApiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L227) +- [coreApiWebdavUploadTUS/checksums.feature:228](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L228) +- [coreApiWebdavUploadTUS/checksums.feature:229](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L229) +- [coreApiWebdavUploadTUS/checksums.feature:234](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L234) +- [coreApiWebdavUploadTUS/checksums.feature:235](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L235) +- [coreApiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L282) +- [coreApiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L283) +- [coreApiWebdavUploadTUS/checksums.feature:284](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L284) +- [coreApiWebdavUploadTUS/checksums.feature:285](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L285) +- [coreApiWebdavUploadTUS/checksums.feature:290](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L290) +- [coreApiWebdavUploadTUS/checksums.feature:291](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L291) +- [coreApiWebdavUploadTUS/optionsRequest.feature:8](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L8) +- [coreApiWebdavUploadTUS/optionsRequest.feature:22](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L22) +- [coreApiWebdavUploadTUS/optionsRequest.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L35) +- [coreApiWebdavUploadTUS/optionsRequest.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L49) +- [coreApiWebdavUploadTUS/uploadToShare.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L176) +- [coreApiWebdavUploadTUS/uploadToShare.feature:177](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L177) +- [coreApiWebdavUploadTUS/uploadToShare.feature:195](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L195) +- [coreApiWebdavUploadTUS/uploadToShare.feature:196](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L196) +- [coreApiWebdavUploadTUS/uploadToShare.feature:214](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L214) +- [coreApiWebdavUploadTUS/uploadToShare.feature:215](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L215) +- [coreApiWebdavUploadTUS/uploadToShare.feature:253](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L253) +- [coreApiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L254) +- [coreApiWebdavUploadTUS/uploadToShare.feature:295](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L295) +- [coreApiWebdavUploadTUS/uploadToShare.feature:296](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L296) #### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) -- [apiWebdavUploadTUS/optionsRequest.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L62) -- [apiWebdavUploadTUS/optionsRequest.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L76) -- [apiWebdavUploadTUS/optionsRequest.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L89) -- [apiWebdavUploadTUS/optionsRequest.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L104) +- [coreApiWebdavUploadTUS/optionsRequest.feature:62](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L62) +- [coreApiWebdavUploadTUS/optionsRequest.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L76) +- [coreApiWebdavUploadTUS/optionsRequest.feature:89](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L89) +- [coreApiWebdavUploadTUS/optionsRequest.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature#L104) #### [Trying to accept a share with invalid ID gives incorrect OCS and HTTP status](https://github.com/owncloud/ocis/issues/2111) -- [apiShareOperationsToShares2/shareAccessByID.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L85) -- [apiShareOperationsToShares2/shareAccessByID.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L86) -- [apiShareOperationsToShares2/shareAccessByID.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L87) -- [apiShareOperationsToShares2/shareAccessByID.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L88) -- [apiShareOperationsToShares2/shareAccessByID.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L89) -- [apiShareOperationsToShares2/shareAccessByID.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L90) -- [apiShareOperationsToShares2/shareAccessByID.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L91) -- [apiShareOperationsToShares2/shareAccessByID.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L92) -- [apiShareOperationsToShares2/shareAccessByID.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L104) -- [apiShareOperationsToShares2/shareAccessByID.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L105) -- [apiShareOperationsToShares2/shareAccessByID.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L136) -- [apiShareOperationsToShares2/shareAccessByID.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L137) -- [apiShareOperationsToShares2/shareAccessByID.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L138) -- [apiShareOperationsToShares2/shareAccessByID.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L139) -- [apiShareOperationsToShares2/shareAccessByID.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L140) -- [apiShareOperationsToShares2/shareAccessByID.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L141) -- [apiShareOperationsToShares2/shareAccessByID.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L142) -- [apiShareOperationsToShares2/shareAccessByID.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L143) -- [apiShareOperationsToShares2/shareAccessByID.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L155) -- [apiShareOperationsToShares2/shareAccessByID.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L156) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L85) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L86) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:87](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L87) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:88](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L88) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:89](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L89) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:90](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L90) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:91](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L91) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:92](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L92) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L104) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L105) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:136](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L136) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:137](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L137) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:138](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L138) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:139](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L139) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:140](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L140) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:141](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L141) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:142](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L142) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:143](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L143) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:155](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L155) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:156](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L156) #### [Shares to deleted group listed in the response](https://github.com/owncloud/ocis/issues/2441) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:534](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L534) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:535](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L535) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:534](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L534) +- [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:535](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L535) #### [copying the file inside Shares folder returns 404](https://github.com/owncloud/ocis/issues/3874) -- [apiWebdavProperties1/copyFile.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L410) -- [apiWebdavProperties1/copyFile.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L411) -- [apiWebdavProperties1/copyFile.feature:416](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L416) -- [apiWebdavProperties1/copyFile.feature:436](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L436) -- [apiWebdavProperties1/copyFile.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L437) -- [apiWebdavProperties1/copyFile.feature:442](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L442) +- [coreApiWebdavProperties1/copyFile.feature:410](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L410) +- [coreApiWebdavProperties1/copyFile.feature:411](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L411) +- [coreApiWebdavProperties1/copyFile.feature:416](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L416) +- [coreApiWebdavProperties1/copyFile.feature:436](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L436) +- [coreApiWebdavProperties1/copyFile.feature:437](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L437) +- [coreApiWebdavProperties1/copyFile.feature:442](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L442) ### Won't fix @@ -708,212 +708,212 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [uploading with old-chunking does not work](https://github.com/owncloud/ocis/issues/1343) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L20) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L21) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L26) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L39) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L40) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L45) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L80) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L86) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L20) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L21) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L26) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:39](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L39) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:40](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L40) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:45](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L45) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:80](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L80) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:81](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) +- [coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature#L86) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) -- [apiWebdavProperties1/copyFile.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L120) -- [apiWebdavProperties1/copyFile.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L121) -- [apiWebdavProperties1/copyFile.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L126) -- [apiWebdavProperties1/createFileFolder.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolder.feature#L98) -- [apiWebdavProperties1/createFileFolder.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolder.feature#L99) -- [apiWebdavProperties1/createFileFolder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolder.feature#L104) -- [apiWebdavUpload1/uploadFile.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L181) -- [apiWebdavUpload1/uploadFile.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L182) -- [apiWebdavUpload1/uploadFile.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L187) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L35) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L36) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L37) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:13](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L13) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L20) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L38) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L39) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L40) -- [apiWebdavMove2/moveFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L287) -- [apiWebdavMove2/moveFile.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L288) -- [apiWebdavMove2/moveFile.feature:293](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L293) +- [coreApiWebdavProperties1/copyFile.feature:120](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L120) +- [coreApiWebdavProperties1/copyFile.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L121) +- [coreApiWebdavProperties1/copyFile.feature:126](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature#L126) +- [coreApiWebdavProperties1/createFileFolder.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature#L98) +- [coreApiWebdavProperties1/createFileFolder.feature:99](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature#L99) +- [coreApiWebdavProperties1/createFileFolder.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature#L104) +- [coreApiWebdavUpload1/uploadFile.feature:181](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature#L181) +- [coreApiWebdavUpload1/uploadFile.feature:182](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature#L182) +- [coreApiWebdavUpload1/uploadFile.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature#L187) +- [coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) +- [coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L35) +- [coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L36) +- [coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L37) +- [coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L13) +- [coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L20) +- [coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L38) +- [coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:39](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L39) +- [coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:40](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L40) +- [coreApiWebdavMove2/moveFile.feature:287](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature#L287) +- [coreApiWebdavMove2/moveFile.feature:288](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature#L288) +- [coreApiWebdavMove2/moveFile.feature:293](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature#L293) #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L21) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L22) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L27) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L40) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L41) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L46) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L81) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L82) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L87) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L19) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L20) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L35) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L36) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L74) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L75) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L21) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:22](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L22) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L27) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:40](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L40) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L41) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:46](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L46) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:81](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L81) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:82](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L82) +- [coreApiWebdavMove1/moveFolderToBlacklistedName.feature:87](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature#L87) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:19](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L19) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L20) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L35) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L36) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:74](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L74) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:75](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L75) #### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L22) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L23) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L28) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L42) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L43) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L48) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L84) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L85) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L90) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L20) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L21) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L37) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L38) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L78) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L79) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:22](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L22) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:23](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L23) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L28) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:42](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L42) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:43](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L43) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:48](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L48) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L84) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L85) +- [coreApiWebdavMove1/moveFolderToExcludedDirectory.feature:90](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature#L90) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:20](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L20) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:21](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L21) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L37) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:38](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L38) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L78) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L79) #### [system configuration options missing](https://github.com/owncloud/ocis/issues/1323) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L31) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L32) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L37) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L71) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L70) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L76) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L31) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L32) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L37) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L71) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:70](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L70) +- [coreApiWebdavUpload1/uploadFileToBlacklistedName.feature:76](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature#L76) #### [Allow public link sharing only for certain groups feature not implemented](https://github.com/owncloud/ocis/issues/4623) -- [apiSharePublicLink3/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/allowGroupToCreatePublicLinks.feature#L35) -- [apiSharePublicLink3/allowGroupToCreatePublicLinks.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink3/allowGroupToCreatePublicLinks.feature#L92) +- [coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature#L35) +- [coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature:92](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature#L92) #### [Preview of text file with UTF content does not render correctly](https://github.com/owncloud/ocis/issues/2570) -- [apiWebdavPreviews/previews.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L211) +- [coreApiWebdavPreviews/previews.feature:211](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L211) #### [Share path in the response is different between share states](https://github.com/owncloud/ocis/issues/2540) -- [apiShareManagementToShares/acceptShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L65) -- [apiShareManagementToShares/acceptShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L88) -- [apiShareManagementToShares/acceptShares.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L214) -- [apiShareManagementToShares/acceptShares.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L237) -- [apiShareManagementToShares/acceptShares.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L275) -- [apiShareManagementToShares/acceptShares.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L309) -- [apiShareManagementToShares/acceptShares.feature:529](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L529) -- [apiShareOperationsToShares2/shareAccessByID.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L123) -- [apiShareOperationsToShares2/shareAccessByID.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L124) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L177) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L178) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L179) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L180) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:196](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L196) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:197](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L197) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:198](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L198) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L199) +- [coreApiShareManagementToShares/acceptShares.feature:65](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L65) +- [coreApiShareManagementToShares/acceptShares.feature:88](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L88) +- [coreApiShareManagementToShares/acceptShares.feature:214](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L214) +- [coreApiShareManagementToShares/acceptShares.feature:237](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L237) +- [coreApiShareManagementToShares/acceptShares.feature:275](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L275) +- [coreApiShareManagementToShares/acceptShares.feature:309](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L309) +- [coreApiShareManagementToShares/acceptShares.feature:529](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature#L529) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:123](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L123) +- [coreApiShareOperationsToShares2/shareAccessByID.feature:124](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L124) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:177](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L177) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:178](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L178) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L179) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L180) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:196](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L196) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:197](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L197) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:198](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L198) +- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:199](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L199) #### [Content-type is not multipart/byteranges when downloading file with Range Header](https://github.com/owncloud/ocis/issues/2677) -- [apiWebdavOperations/downloadFile.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L229) -- [apiWebdavOperations/downloadFile.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L230) -- [apiWebdavOperations/downloadFile.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L235) +- [coreApiWebdavOperations/downloadFile.feature:229](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L229) +- [coreApiWebdavOperations/downloadFile.feature:230](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L230) +- [coreApiWebdavOperations/downloadFile.feature:235](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L235) #### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) +- [coreApiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) +- [coreApiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) +- [coreApiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) #### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099) -- [apiWebdavMove1/moveFolder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L27) -- [apiWebdavMove1/moveFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L45) -- [apiWebdavMove1/moveFolder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L63) -- [apiWebdavMove2/moveFile.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L224) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L25) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L41) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L80) +- [coreApiWebdavMove1/moveFolder.feature:27](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature#L27) +- [coreApiWebdavMove1/moveFolder.feature:45](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature#L45) +- [coreApiWebdavMove1/moveFolder.feature:63](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature#L63) +- [coreApiWebdavMove2/moveFile.feature:224](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature#L224) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L25) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L41) +- [coreApiWebdavMove2/moveFileToBlacklistedName.feature:80](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature#L80) #### [REPORT method on spaces returns an incorrect d:href response](https://github.com/owncloud/ocis/issues/3111) -- [apiFavorites/favorites.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L121) -- [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) -- [apiFavorites/favorites.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L270) +- [coreApiFavorites/favorites.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L121) +- [coreApiFavorites/favorites.feature:148](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L148) +- [coreApiFavorites/favorites.feature:270](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiFavorites/favorites.feature#L270) #### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) -- [apiWebdavOperations/search.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L274) -- [apiWebdavOperations/search.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L291) -- [apiWebdavOperations/search.feature:317](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L317) +- [coreApiWebdavOperations/search.feature:274](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L274) +- [coreApiWebdavOperations/search.feature:291](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L291) +- [coreApiWebdavOperations/search.feature:317](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/search.feature#L317) #### [Cannot disable the dav propfind depth infinity for resources](https://github.com/owncloud/ocis/issues/3720) -- [apiWebdavOperations/listFiles.feature:383](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L383) -- [apiWebdavOperations/listFiles.feature:384](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L384) -- [apiWebdavOperations/listFiles.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L389) -- [apiWebdavOperations/listFiles.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L408) -- [apiWebdavOperations/listFiles.feature:413](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L413) -- [apiWebdavOperations/listFiles.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L427) -- [apiWebdavOperations/listFiles.feature:428](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L428) -- [apiWebdavOperations/listFiles.feature:433](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L433) +- [coreApiWebdavOperations/listFiles.feature:383](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L383) +- [coreApiWebdavOperations/listFiles.feature:384](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L384) +- [coreApiWebdavOperations/listFiles.feature:389](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L389) +- [coreApiWebdavOperations/listFiles.feature:408](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L408) +- [coreApiWebdavOperations/listFiles.feature:413](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L413) +- [coreApiWebdavOperations/listFiles.feature:427](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L427) +- [coreApiWebdavOperations/listFiles.feature:428](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L428) +- [coreApiWebdavOperations/listFiles.feature:433](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L433) #### [Renaming resource to excluded directory name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3102) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L26) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L43) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L84) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:26](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L26) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:43](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L43) +- [coreApiWebdavMove2/moveFileToExcludedDirectory.feature:84](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature#L84) ### [graph/users: enable/disable users](https://github.com/owncloud/ocis/issues/3064) -- [apiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L35) -- [apiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L36) -- [apiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L41) +- [coreApiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L35) +- [coreApiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L36) +- [coreApiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L41) #### [OCS status code zero](https://github.com/owncloud/ocis/issues/3621) -- [apiShareManagementToShares/moveReceivedShare.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L32) +- [coreApiShareManagementToShares/moveReceivedShare.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature#L32) #### [HTTP status code differ while deleting file of another user's trash bin](https://github.com/owncloud/ocis/issues/3544) -- [apiTrashbin/trashbinDelete.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L109) +- [coreApiTrashbin/trashbinDelete.feature:109](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L109) #### [Problem accessing trashbin with personal space id](https://github.com/owncloud/ocis/issues/3639) -- [apiTrashbin/trashbinDelete.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L35) -- [apiTrashbin/trashbinDelete.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L36) -- [apiTrashbin/trashbinDelete.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L58) -- [apiTrashbin/trashbinDelete.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L85) -- [apiTrashbin/trashbinDelete.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L132) -- [apiTrashbin/trashbinDelete.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L155) -- [apiTrashbin/trashbinDelete.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L180) -- [apiTrashbin/trashbinDelete.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L205) -- [apiTrashbin/trashbinDelete.feature:242](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L242) -- [apiTrashbin/trashbinDelete.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L279) -- [apiTrashbin/trashbinDelete.feature:328](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L328) -- [apiTrashbin/trashbinFilesFolders.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L25) -- [apiTrashbin/trashbinFilesFolders.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L41) -- [apiTrashbin/trashbinFilesFolders.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L60) -- [apiTrashbin/trashbinFilesFolders.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L81) -- [apiTrashbin/trashbinFilesFolders.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L100) -- [apiTrashbin/trashbinFilesFolders.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L136) -- [apiTrashbin/trashbinFilesFolders.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L159) -- [apiTrashbin/trashbinFilesFolders.feature:339](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L339) -- [apiTrashbin/trashbinFilesFolders.feature:340](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L340) -- [apiTrashbin/trashbinFilesFolders.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L341) -- [apiTrashbin/trashbinFilesFolders.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L346) -- [apiTrashbin/trashbinFilesFolders.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L347) -- [apiTrashbin/trashbinFilesFolders.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L348) -- [apiTrashbin/trashbinFilesFolders.feature:349](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L349) -- [apiTrashbin/trashbinFilesFolders.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L350) -- [apiTrashbin/trashbinFilesFolders.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L351) -- [apiTrashbin/trashbinFilesFolders.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L368) -- [apiTrashbin/trashbinFilesFolders.feature:388](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L388) -- [apiTrashbin/trashbinFilesFolders.feature:442](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L442) -- [apiTrashbin/trashbinFilesFolders.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L479) +- [coreApiTrashbin/trashbinDelete.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L35) +- [coreApiTrashbin/trashbinDelete.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L36) +- [coreApiTrashbin/trashbinDelete.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L58) +- [coreApiTrashbin/trashbinDelete.feature:85](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L85) +- [coreApiTrashbin/trashbinDelete.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L132) +- [coreApiTrashbin/trashbinDelete.feature:155](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L155) +- [coreApiTrashbin/trashbinDelete.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L180) +- [coreApiTrashbin/trashbinDelete.feature:205](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L205) +- [coreApiTrashbin/trashbinDelete.feature:242](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L242) +- [coreApiTrashbin/trashbinDelete.feature:279](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L279) +- [coreApiTrashbin/trashbinDelete.feature:328](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L328) +- [coreApiTrashbin/trashbinFilesFolders.feature:25](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L25) +- [coreApiTrashbin/trashbinFilesFolders.feature:41](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L41) +- [coreApiTrashbin/trashbinFilesFolders.feature:60](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L60) +- [coreApiTrashbin/trashbinFilesFolders.feature:81](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L81) +- [coreApiTrashbin/trashbinFilesFolders.feature:100](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L100) +- [coreApiTrashbin/trashbinFilesFolders.feature:136](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L136) +- [coreApiTrashbin/trashbinFilesFolders.feature:159](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L159) +- [coreApiTrashbin/trashbinFilesFolders.feature:339](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L339) +- [coreApiTrashbin/trashbinFilesFolders.feature:340](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L340) +- [coreApiTrashbin/trashbinFilesFolders.feature:341](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L341) +- [coreApiTrashbin/trashbinFilesFolders.feature:346](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L346) +- [coreApiTrashbin/trashbinFilesFolders.feature:347](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L347) +- [coreApiTrashbin/trashbinFilesFolders.feature:348](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L348) +- [coreApiTrashbin/trashbinFilesFolders.feature:349](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L349) +- [coreApiTrashbin/trashbinFilesFolders.feature:350](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L350) +- [coreApiTrashbin/trashbinFilesFolders.feature:351](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L351) +- [coreApiTrashbin/trashbinFilesFolders.feature:368](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L368) +- [coreApiTrashbin/trashbinFilesFolders.feature:388](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L388) +- [coreApiTrashbin/trashbinFilesFolders.feature:442](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L442) +- [coreApiTrashbin/trashbinFilesFolders.feature:479](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature#L479) Note: always have an empty line at the end of this file. The bash script that processes this file requires that the last line has a newline on the end. diff --git a/tests/acceptance/features/bootstrap/AppConfigurationContext.php b/tests/acceptance/features/bootstrap/AppConfigurationContext.php new file mode 100644 index 00000000000..f44177d8c81 --- /dev/null +++ b/tests/acceptance/features/bootstrap/AppConfigurationContext.php @@ -0,0 +1,591 @@ + + * @author Sergio Bertolin + * @author Phillip Davis + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use GuzzleHttp\Exception\GuzzleException; +use PHPUnit\Framework\Assert; +use TestHelpers\AppConfigHelper; +use TestHelpers\OcsApiHelper; +use Behat\Gherkin\Node\TableNode; +use Behat\Behat\Context\Context; + +/** + * AppConfiguration trait + */ +class AppConfigurationContext implements Context { + /** + * @var FeatureContext + */ + private $featureContext; + + /** + * @When /^the administrator sets parameter "([^"]*)" of app "([^"]*)" to ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $parameter + * @param string $app + * @param string $value + * + * @return void + * @throws Exception + */ + public function adminSetsServerParameterToUsingAPI( + string $parameter, + string $app, + string $value + ):void { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + $value = \trim($value, $value[0]); + $this->modifyAppConfig($app, $parameter, $value); + } + + /** + * @Given /^parameter "([^"]*)" of app "([^"]*)" has been set to ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $parameter + * @param string $app + * @param string $value + * + * @return void + * @throws Exception + */ + public function serverParameterHasBeenSetTo(string $parameter, string $app, string $value):void { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + if (\TestHelpers\OcisHelper::isTestingOnOcisOrReva()) { + return; + } + $value = \trim($value, $value[0]); + $this->modifyAppConfig($app, $parameter, $value); + $this->featureContext->clearStatusCodeArrays(); + } + + /** + * @Then the capabilities setting of :capabilitiesApp path :capabilitiesPath should be :expectedValue + * @Given the capabilities setting of :capabilitiesApp path :capabilitiesPath has been confirmed to be :expectedValue + * + * @param string $capabilitiesApp the "app" name in the capabilities response + * @param string $capabilitiesPath the path to the element + * @param string $expectedValue + * + * @return void + * @throws Exception + */ + public function theCapabilitiesSettingOfAppParameterShouldBe( + string $capabilitiesApp, + string $capabilitiesPath, + string $expectedValue + ):void { + $this->theAdministratorGetsCapabilitiesCheckResponse(); + $actualValue = $this->getAppParameter($capabilitiesApp, $capabilitiesPath); + + Assert::assertEquals( + $expectedValue, + $actualValue, + __METHOD__ + . " $capabilitiesApp path $capabilitiesPath should be $expectedValue but is $actualValue" + ); + } + + /** + * @param string $capabilitiesApp the "app" name in the capabilities response + * @param string $capabilitiesPath the path to the element + * + * @return string + * @throws Exception + */ + public function getAppParameter(string $capabilitiesApp, string $capabilitiesPath):string { + return $this->getParameterValueFromXml( + $this->getCapabilitiesXml(__METHOD__), + $capabilitiesApp, + $capabilitiesPath + ); + } + + /** + * @When user :username retrieves the capabilities using the capabilities API + * + * @param string $username + * + * @return void + * @throws GuzzleException + * @throws JsonException + */ + public function userGetsCapabilities(string $username):void { + $user = $this->featureContext->getActualUsername($username); + $password = $this->featureContext->getPasswordForUser($user); + $this->featureContext->setResponse( + OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $user, + $password, + 'GET', + '/cloud/capabilities', + $this->featureContext->getStepLineRef(), + [], + $this->featureContext->getOcsApiVersion() + ) + ); + } + + /** + * @Given user :username has retrieved the capabilities + * + * @param string $username + * + * @return void + * @throws Exception + */ + public function userGetsCapabilitiesCheckResponse(string $username):void { + $this->userGetsCapabilities($username); + $statusCode = $this->featureContext->getResponse()->getStatusCode(); + if ($statusCode !== 200) { + throw new \Exception( + __METHOD__ + . " user $username returned unexpected status $statusCode" + ); + } + } + + /** + * @When the user retrieves the capabilities using the capabilities API + * + * @return void + */ + public function theUserGetsCapabilities():void { + $this->userGetsCapabilities($this->featureContext->getCurrentUser()); + } + + /** + * @Given the user has retrieved the capabilities + * + * @return void + * @throws Exception + */ + public function theUserGetsCapabilitiesCheckResponse():void { + $this->userGetsCapabilitiesCheckResponse($this->featureContext->getCurrentUser()); + } + + /** + * @return string + * @throws Exception + */ + public function getAdminUsernameForCapabilitiesCheck():string { + if (\TestHelpers\OcisHelper::isTestingOnReva()) { + // When testing on reva we don't have a user called "admin" to use + // to access the capabilities. So create an ordinary user on-the-fly + // with a default password. That user should be able to get a + // capabilities response that the test can process. + $adminUsername = "PseudoAdminForRevaTest"; + $createdUsers = $this->featureContext->getCreatedUsers(); + if (!\array_key_exists($adminUsername, $createdUsers)) { + $this->featureContext->createUser($adminUsername); + } + } else { + $adminUsername = $this->featureContext->getAdminUsername(); + } + return $adminUsername; + } + + /** + * @When the administrator retrieves the capabilities using the capabilities API + * + * @return void + */ + public function theAdministratorGetsCapabilities():void { + $this->userGetsCapabilities($this->getAdminUsernameForCapabilitiesCheck()); + } + + /** + * @Given the administrator has retrieved the capabilities + * + * @return void + * @throws Exception + */ + public function theAdministratorGetsCapabilitiesCheckResponse():void { + $this->userGetsCapabilitiesCheckResponse($this->getAdminUsernameForCapabilitiesCheck()); + } + + /** + * @param string $exceptionText text to put at the front of exception messages + * + * @return SimpleXMLElement latest retrieved capabilities in XML format + * @throws Exception + */ + public function getCapabilitiesXml(string $exceptionText = ''): SimpleXMLElement { + if ($exceptionText === '') { + $exceptionText = __METHOD__; + } + return $this->featureContext->getResponseXml(null, $exceptionText)->data->capabilities; + } + + /** + * @param string $exceptionText text to put at the front of exception messages + * + * @return SimpleXMLElement latest retrieved version data in XML format + * @throws Exception + */ + public function getVersionXml(string $exceptionText = ''): SimpleXMLElement { + if ($exceptionText === '') { + $exceptionText = __METHOD__; + } + return $this->featureContext->getResponseXml(null, $exceptionText)->data->version; + } + + /** + * @param SimpleXMLElement $xml of the capabilities + * @param string $capabilitiesApp the "app" name in the capabilities response + * @param string $capabilitiesPath the path to the element + * + * @return string + */ + public function getParameterValueFromXml( + SimpleXMLElement $xml, + string $capabilitiesApp, + string $capabilitiesPath + ):string { + $path_to_element = \explode('@@@', $capabilitiesPath); + $answeredValue = $xml->{$capabilitiesApp}; + foreach ($path_to_element as $element) { + $nameIndexParts = \explode('[', $element); + if (isset($nameIndexParts[1])) { + // This part of the path should be something like "some_element[1]" + // Separately extract the name and the index + $name = $nameIndexParts[0]; + $index = (int) \explode(']', $nameIndexParts[1])[0]; + // and use those to construct the reference into the next XML level + $answeredValue = $answeredValue->{$name}[$index]; + } else { + if ($element !== "") { + $answeredValue = $answeredValue->{$element}; + } + } + } + + return (string) $answeredValue; + } + + /** + * @param SimpleXMLElement $xml of the capabilities + * @param string $capabilitiesApp the "app" name in the capabilities response + * @param string $capabilitiesPath the path to the element + * + * @return boolean + */ + public function parameterValueExistsInXml( + SimpleXMLElement $xml, + string $capabilitiesApp, + string $capabilitiesPath + ):bool { + $path_to_element = \explode('@@@', $capabilitiesPath); + $answeredValue = $xml->{$capabilitiesApp}; + + foreach ($path_to_element as $element) { + $nameIndexParts = \explode('[', $element); + if (isset($nameIndexParts[1])) { + // This part of the path should be something like "some_element[1]" + // Separately extract the name and the index + $name = $nameIndexParts[0]; + $index = (int) \explode(']', $nameIndexParts[1])[0]; + // and use those to construct the reference into the next XML level + if (isset($answeredValue->{$name}[$index])) { + $answeredValue = $answeredValue->{$name}[$index]; + } else { + // The path ends at this level + return false; + } + } else { + if (isset($answeredValue->{$element})) { + $answeredValue = $answeredValue->{$element}; + } else { + // The path ends at this level + return false; + } + } + } + + return true; + } + + /** + * @param string $app + * @param string $parameter + * @param string $value + * + * @return void + * @throws Exception + */ + public function modifyAppConfig(string $app, string $parameter, string $value):void { + AppConfigHelper::modifyAppConfig( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $app, + $parameter, + $value, + $this->featureContext->getStepLineRef(), + $this->featureContext->getOcsApiVersion() + ); + } + + /** + * @param array $appParameterValues + * + * @return void + * @throws Exception + */ + public function modifyAppConfigs(array $appParameterValues):void { + AppConfigHelper::modifyAppConfigs( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $appParameterValues, + $this->featureContext->getStepLineRef(), + $this->featureContext->getOcsApiVersion() + ); + } + + /** + * @When the administrator adds url :url as trusted server using the testing API + * + * @param string $url + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorAddsUrlAsTrustedServerUsingTheTestingApi(string $url):void { + $adminUser = $this->featureContext->getAdminUsername(); + $response = OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $adminUser, + $this->featureContext->getAdminPassword(), + 'POST', + "/apps/testing/api/v1/trustedservers", + $this->featureContext->getStepLineRef(), + ['url' => $this->featureContext->substituteInLineCodes($url)] + ); + $this->featureContext->setResponse($response); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * Return text that contains the details of the URL, including any differences due to inline codes + * + * @param string $url + * + * @return string + */ + private function getUrlStringForMessage(string $url):string { + $text = $url; + $expectedUrl = $this->featureContext->substituteInLineCodes($url); + if ($expectedUrl !== $url) { + $text .= " ($expectedUrl)"; + } + return $text; + } + + /** + * @param string $url + * + * @return string + */ + private function getNotTrustedServerMessage(string $url):string { + return + "URL " + . $this->getUrlStringForMessage($url) + . " is not a trusted server but should be"; + } + + /** + * @Then url :url should be a trusted server + * + * @param string $url + * + * @return void + * @throws Exception + */ + public function urlShouldBeATrustedServer(string $url):void { + $trustedServers = $this->featureContext->getTrustedServers(); + foreach ($trustedServers as $server => $id) { + if ($server === $this->featureContext->substituteInLineCodes($url)) { + return; + } + } + Assert::fail($this->getNotTrustedServerMessage($url)); + } + + /** + * @Then the trusted server list should include these urls: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theTrustedServerListShouldIncludeTheseUrls(TableNode $table):void { + $trustedServers = $this->featureContext->getTrustedServers(); + $expected = $table->getColumnsHash(); + + foreach ($expected as $server) { + $found = false; + foreach ($trustedServers as $url => $id) { + if ($url === $this->featureContext->substituteInLineCodes($server['url'])) { + $found = true; + break; + } + } + if (!$found) { + Assert::fail($this->getNotTrustedServerMessage($server['url'])); + } + } + } + + /** + * @Given the administrator has added url :url as trusted server + * + * @param string $url + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function theAdministratorHasAddedUrlAsTrustedServer(string $url):void { + $this->theAdministratorAddsUrlAsTrustedServerUsingTheTestingApi($url); + $status = $this->featureContext->getResponse()->getStatusCode(); + if ($status !== 201) { + throw new \Exception( + __METHOD__ . + " Could not add trusted server " . $this->getUrlStringForMessage($url) + . ". The request failed with status $status" + ); + } + } + + /** + * @When the administrator deletes url :url from trusted servers using the testing API + * + * @param string $url + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorDeletesUrlFromTrustedServersUsingTheTestingApi(string $url):void { + $adminUser = $this->featureContext->getAdminUsername(); + $response = OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $adminUser, + $this->featureContext->getAdminPassword(), + 'DELETE', + "/apps/testing/api/v1/trustedservers", + $this->featureContext->getStepLineRef(), + ['url' => $this->featureContext->substituteInLineCodes($url)] + ); + $this->featureContext->setResponse($response); + } + + /** + * @Then url :url should not be a trusted server + * + * @param string $url + * + * @return void + * @throws Exception + */ + public function urlShouldNotBeATrustedServer(string $url):void { + $trustedServers = $this->featureContext->getTrustedServers(); + foreach ($trustedServers as $server => $id) { + if ($server === $this->featureContext->substituteInLineCodes($url)) { + Assert::fail( + "URL " . $this->getUrlStringForMessage($url) + . " is a trusted server but is not expected to be" + ); + } + } + } + + /** + * @When the administrator deletes all trusted servers using the testing API + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorDeletesAllTrustedServersUsingTheTestingApi():void { + $adminUser = $this->featureContext->getAdminUsername(); + $response = OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $adminUser, + $this->featureContext->getAdminPassword(), + 'DELETE', + "/apps/testing/api/v1/trustedservers/all", + $this->featureContext->getStepLineRef() + ); + $this->featureContext->setResponse($response); + } + + /** + * @Given the trusted server list is cleared + * + * @return void + * @throws Exception + */ + public function theTrustedServerListIsCleared():void { + $this->theAdministratorDeletesAllTrustedServersUsingTheTestingApi(); + $statusCode = $this->featureContext->getResponse()->getStatusCode(); + if ($statusCode !== 204) { + $contents = $this->featureContext->getResponse()->getBody()->getContents(); + throw new \Exception( + __METHOD__ + . " Failed to clear all trusted servers" . $contents + ); + } + } + + /** + * @Then the trusted server list should be empty + * + * @return void + * @throws Exception + */ + public function theTrustedServerListShouldBeEmpty():void { + $trustedServers = $this->featureContext->getTrustedServers(); + Assert::assertEmpty( + $trustedServers, + __METHOD__ . " Trusted server list is not empty" + ); + } + + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function setUpScenario(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/AuthContext.php b/tests/acceptance/features/bootstrap/AuthContext.php new file mode 100644 index 00000000000..24c57b657d0 --- /dev/null +++ b/tests/acceptance/features/bootstrap/AuthContext.php @@ -0,0 +1,1158 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use TestHelpers\HttpRequestHelper; +use Behat\Gherkin\Node\TableNode; +use Behat\Behat\Context\Context; +use TestHelpers\SetupHelper; + +/** + * Authentication functions + */ +class AuthContext implements Context { + /** + * @var string + */ + private $clientToken; + + /** + * @var string + */ + private $appToken; + + /** + * @var array + */ + private $appTokens; + + /** + * @var boolean + */ + private $tokenAuthHasBeenSet = false; + + /** + * @var FeatureContext + */ + private $featureContext; + + /** + * @var string 'true' or 'false' or '' + */ + private $tokenAuthHasBeenSetTo = ''; + + /** + * @return string + */ + public function getTokenAuthHasBeenSetTo():string { + return $this->tokenAuthHasBeenSetTo; + } + + /** + * get the client token that was last generated + * app acceptance tests that have their own step code may need to use this + * + * @return string client token + */ + public function getClientToken():string { + return $this->clientToken; + } + + /** + * get the app token that was last generated + * app acceptance tests that have their own step code may need to use this + * + * @return string app token + */ + public function getAppToken():string { + return $this->appToken; + } + + /** + * get the app token that was last generated + * app acceptance tests that have their own step code may need to use this + * + * @return array app tokens + */ + public function getAppTokens():array { + return $this->appTokens; + } + + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function setUpScenario(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + + // Reset ResponseXml + $this->featureContext->setResponseXml([]); + + // Initialize SetupHelper class + SetupHelper::init( + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + } + + /** + * @When a user requests :url with :method and no authentication + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userRequestsURLWith(string $url, string $method):void { + $this->sendRequest($url, $method); + } + + /** + * @When user :user requests :url with :method and no authentication + * + * @param string $user + * @param string $url + * @param string $method + * + * @return void + * @throws JsonException + */ + public function userRequestsURLWithNoAuth(string $user, string $url, string $method):void { + $userRenamed = $this->featureContext->getActualUsername($user); + $url = $this->featureContext->substituteInLineCodes($url, $userRenamed); + $this->sendRequest($url, $method); + } + + /** + * @Given a user has requested :url with :method and no authentication + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userHasRequestedURLWith(string $url, string $method):void { + $this->sendRequest($url, $method); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * Verifies status code + * + * @param string $ocsCode + * @param string $httpCode + * @param string $endPoint + * + * @return void + * @throws Exception + */ + public function verifyStatusCode(string $ocsCode, string $httpCode, string $endPoint):void { + if ($ocsCode !== null) { + $this->featureContext->ocsContext->theOCSStatusCodeShouldBe( + $ocsCode, + $message = "Got unexpected OCS code while sending request to endpoint " . $endPoint + ); + } + $this->featureContext->theHTTPStatusCodeShouldBe( + $httpCode, + $message = "Got unexpected HTTP code while sending request to endpoint " . $endPoint + ); + } + + /** + * @When a user requests these endpoints with :method with body :body and no authentication about user :user + * + * @param string $method + * @param ?string $body + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithBodyAndNoAuthThenStatusCodeAboutUser(string $method, ?string $body, ?string $ofUser, TableNode $table):void { + $ofUser = \strtolower($this->featureContext->getActualUsername($ofUser)); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastOCSStatusCodesArray(); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $ofUser + ); + $this->sendRequest($row['endpoint'], $method, null, false, $body); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When a user requests these endpoints with :method with no authentication about user :user + * + * @param string $method + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithoutBodyAndNoAuth(string $method, string $ofUser, TableNode $table):void { + $this->userRequestsEndpointsWithBodyAndNoAuthThenStatusCodeAboutUser( + $method, + null, + $ofUser, + $table + ); + } + + /** + * @When a user requests these endpoints with :method and no authentication + * + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithNoAuthentication(string $method, TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastOCSStatusCodesArray(); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + foreach ($table->getHash() as $row) { + $this->sendRequest($row['endpoint'], $method); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the user :user requests these endpoints with :method with basic auth + * + * @param string $user + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithBasicAuth(string $user, string $method, TableNode $table):void { + $user = $this->featureContext->getActualUsername($user); + $this->userRequestsEndpointsWithPassword($user, $method, null, $table); + } + + /** + * @When the user :user requests these endpoints with :method using basic auth and generated app password about user :ofUser + * + * @param string $user + * @param string $method + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithBasicAuthAndGeneratedPassword(string $user, string $method, string $ofUser, TableNode $table):void { + $this->requestEndpointsWithBasicAuthAndGeneratedPassword($user, $method, $ofUser, $table); + } + + /** + * @When /^the user "([^"]*)" requests these endpoints with "([^"]*)" to (?:get|set) property "([^"]*)" using basic auth and generated app password about user "([^"]*)"$/ + * + * @param string $user + * @param string $method + * @param string $property + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithBasicAuthAndGeneratedPasswordWithProperty( + string $user, + string $method, + string $property, + string $ofUser, + TableNode $table + ):void { + $this->requestEndpointsWithBasicAuthAndGeneratedPassword( + $user, + $method, + $ofUser, + $table, + null, + $property + ); + } + + /** + * @When /^the user "([^"]*)" requests these endpoints with "([^"]*)" to (?:get|set) property "([^"]*)" with password "([^"]*)" about user "([^"]*)"$/ + * + * @param string $user + * @param string $method + * @param string $property + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithPasswordWithProperty( + string $user, + string $method, + string $property, + string $ofUser, + TableNode $table + ):void { + $this->userRequestsEndpointsWithPassword( + $user, + $method, + $ofUser, + $table, + $property + ); + } + + /** + * @When the user :user requests these endpoints with :method with body :body using basic auth and generated app password about user :ofUser + * + * @param string $user + * @param string $method + * @param string $body + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithBasicAuthAndGeneratedPasswordWithBody( + string $user, + string $method, + string $body, + string $ofUser, + TableNode $table + ):void { + $header = []; + if ($method === 'MOVE' || $method === 'COPY') { + $header['Destination'] = '/path/to/destination'; + } + + $this->requestEndpointsWithBasicAuthAndGeneratedPassword( + $user, + $method, + $ofUser, + $table, + $body, + null, + $header, + ); + } + + /** + * @param string $user requesting user + * @param string $method http method + * @param string $ofUser resource owner + * @param TableNode $table endpoints table + * @param string|null $body body for request + * @param string|null $property property to get + * @param Array|null $header request header + * + * @return void + * @throws Exception + */ + public function requestEndpointsWithBasicAuthAndGeneratedPassword( + string $user, + string $method, + string $ofUser, + TableNode $table, + ?string $body = null, + ?string $property = null, + ?array $header = null + ):void { + $user = $this->featureContext->getActualUsername($user); + $ofUser = \strtolower($this->featureContext->getActualUsername($ofUser)); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastOCSStatusCodesArray(); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + if ($body === null && $property !== null) { + $body = $this->featureContext->getBodyForOCSRequest($method, $property); + } + + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $ofUser + ); + $this->userRequestsURLWithUsingBasicAuth($user, $row['endpoint'], $method, $this->appToken, $body, $header); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :user requests these endpoints with :method using password :password + * + * @param string $user + * @param string $method + * @param string|null $password + * @param TableNode $table + * @param string|null $property + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsWithPassword( + string $user, + string $method, + ?string $password, + TableNode $table, + ?string $property = null + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->emptyLastOCSStatusCodesArray(); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + foreach ($table->getHash() as $row) { + $body = null; + if ($property !== null) { + $body = $this->featureContext->getBodyForOCSRequest($method, $property); + } + $this->userRequestsURLWithUsingBasicAuth($user, $row['endpoint'], $method, $password, $body); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the administrator requests these endpoint with :method + * + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function adminRequestsEndpoint(string $method, TableNode $table):void { + $this->adminRequestsEndpointsWithBodyWithPassword($method, null, null, null, $table); + } + + /** + * @When the administrator requests these endpoints with :method with body :body using password :password about user :ofUser + * + * @param string|null $method + * @param string|null $body + * @param string|null $password + * @param string|null $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function adminRequestsEndpointsWithBodyWithPassword( + ?string $method, + ?string $body, + ?string $password, + ?string $ofUser, + TableNode $table + ):void { + $ofUser = $this->featureContext->getActualUsername($ofUser); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $ofUser + ); + $this->userRequestsURLWithUsingBasicAuth( + $this->featureContext->getAdminUsername(), + $row['endpoint'], + $method, + $password, + $body + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the administrator requests these endpoints with :method using password :password about user :ofUser + * + * @param string $method + * @param string $password + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function adminRequestsEndpointsWithPassword( + string $method, + string $password, + string $ofUser, + TableNode $table + ):void { + $this->adminRequestsEndpointsWithBodyWithPassword($method, null, $password, $ofUser, $table); + } + + /** + * @When user :user requests these endpoints with :method using basic token auth + * + * @param string $user + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function whenUserWithNewClientTokenRequestsForEndpointUsingBasicTokenAuth(string $user, string $method, TableNode $table):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $this->userRequestsURLWithUsingBasicTokenAuth($user, $row['endpoint'], $method); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the user requests these endpoints with :method using a new browser session + * + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsTheseEndpointsUsingNewBrowserSession(string $method, TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $this->userRequestsURLWithBrowserSession($row['endpoint'], $method); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the user requests these endpoints with :method using the generated app password about user :user + * + * @param string $method + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsUsingTheGeneratedAppPasswordThenStatusCodeAboutUser(string $method, string $user, TableNode $table):void { + $user = \strtolower($this->featureContext->getActualUsername($user)); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $user + ); + $this->userRequestsURLWithUsingAppPassword($row['endpoint'], $method); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When the user requests these endpoints with :method using the generated app password + * + * @param string $method + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsEndpointsUsingTheGeneratedAppPassword(string $method, TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $this->userRequestsURLWithUsingAppPassword($row['endpoint'], $method); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @param string $url + * @param string $method + * @param string|null $authHeader + * @param bool $useCookies + * @param string|null $body + * @param array|null $headers + * + * @return void + */ + public function sendRequest( + string $url, + string $method, + ?string $authHeader = null, + bool $useCookies = false, + ?string $body = null, + ?array $headers = [] + ):void { + // reset responseXml + $this->featureContext->setResponseXml([]); + + $fullUrl = $this->featureContext->getBaseUrl() . $url; + + $cookies = null; + if ($useCookies) { + $cookies = $this->featureContext->getCookieJar(); + } + + if ($authHeader) { + $headers['Authorization'] = $authHeader; + } + $headers['OCS-APIREQUEST'] = 'true'; + if (isset($this->requestToken)) { + $headers['requesttoken'] = $this->featureContext->getRequestToken(); + } + $this->featureContext->setResponse( + HttpRequestHelper::sendRequest( + $fullUrl, + $this->featureContext->getStepLineRef(), + $method, + null, + null, + $headers, + $body, + null, + $cookies + ) + ); + } + + /** + * Use the private API to generate an app password + * + * @param string $name + * + * @return void + */ + public function userGeneratesNewAppPasswordNamed(string $name):void { + $url = $this->featureContext->getBaseUrl() . '/index.php/settings/personal/authtokens'; + $body = ['name' => $name]; + $headers = [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'OCS-APIREQUEST' => 'true', + 'requesttoken' => $this->featureContext->getRequestToken(), + 'X-Requested-With' => 'XMLHttpRequest' + ]; + $this->featureContext->setResponse( + HttpRequestHelper::post( + $url, + $this->featureContext->getStepLineRef(), + null, + null, + $headers, + $body, + null, + $this->featureContext->getCookieJar() + ) + ); + $token = \json_decode($this->featureContext->getResponse()->getBody()->getContents()); + $this->appToken = $token->token; + $this->appTokens[$token->deviceToken->name] + = ["id" => $token->deviceToken->id, "token" => $token->token]; + } + + /** + * Use the private API to generate an app password + * + * @param string $name + * + * @return void + */ + public function userDeletesAppPasswordNamed(string $name):void { + $url = $this->featureContext->getBaseUrl() . '/index.php/settings/personal/authtokens/' . $this->appTokens[$name]["id"]; + $headers = [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'OCS-APIREQUEST' => 'true', + 'requesttoken' => $this->featureContext->getRequestToken(), + 'X-Requested-With' => 'XMLHttpRequest' + ]; + $this->featureContext->setResponse( + HttpRequestHelper::delete( + $url, + $this->featureContext->getStepLineRef(), + null, + null, + $headers, + null, + null, + $this->featureContext->getCookieJar() + ) + ); + } + + /** + * @Given the user has generated a new app password named :name + * + * @param string $name + * + * @return void + */ + public function aNewAppPasswordHasBeenGenerated(string $name):void { + $this->userGeneratesNewAppPasswordNamed($name); + $this->featureContext->theHTTPStatusCodeShouldBe(200); + } + + /** + * @Given the user has deleted the app password named :name + * + * @param string $name + * + * @return void + */ + public function aNewAppPasswordHasBeenDeleted(string $name):void { + $this->userDeletesAppPasswordNamed($name); + $this->featureContext->theHTTPStatusCodeShouldBe(200); + } + + /** + * @When user :user generates a new client token using the token API + * @Given a new client token for :user has been generated + * + * @param string $user + * + * @return void + */ + public function aNewClientTokenHasBeenGenerated(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $body = \json_encode( + [ + 'user' => $this->featureContext->getActualUsername($user), + 'password' => $this->featureContext->getPasswordForUser($user), + ] + ); + $headers = ['Content-Type' => 'application/json']; + $url = $this->featureContext->getBaseUrl() . '/token/generate'; + $this->featureContext->setResponse( + HttpRequestHelper::post( + $url, + $this->featureContext->getStepLineRef(), + null, + null, + $headers, + $body + ) + ); + $this->featureContext->theHTTPStatusCodeShouldBe("200"); + $this->clientToken + = \json_decode($this->featureContext->getResponse()->getBody()->getContents())->token; + } + + /** + * @When the administrator generates a new client token using the token API + * @Given a new client token for the administrator has been generated + * + * @return void + */ + public function aNewClientTokenForTheAdministratorHasBeenGenerated():void { + $admin = $this->featureContext->getAdminUsername(); + $this->aNewClientTokenHasBeenGenerated($admin); + } + + /** + * @When user :user requests :url with :method using basic auth + * + * @param string $user + * @param string $url + * @param string $method + * @param string|null $password + * @param string|null $body + * @param array|null $header + * + * @return void + */ + public function userRequestsURLWithUsingBasicAuth( + string $user, + string $url, + string $method, + ?string $password = null, + ?string $body = null, + ?array $header = null + ):void { + $userRenamed = $this->featureContext->getActualUsername($user); + $url = $this->featureContext->substituteInLineCodes( + $url, + $userRenamed + ); + if ($password === null) { + $authString = "$userRenamed:" . $this->featureContext->getPasswordForUser($user); + } else { + $authString = $userRenamed . ":" . $this->featureContext->getActualPassword($password); + } + $this->sendRequest( + $url, + $method, + 'basic ' . \base64_encode($authString), + false, + $body, + $header + ); + } + + /** + * @When user :user requests :url with :method using basic auth and with headers + * + * @param string $user + * @param string $url + * @param string $method + * @param TableNode $headersTable + * + * @return void + * @throws Exception + */ + public function userRequestsURLWithUsingBasicAuthAndDepthHeader(string $user, string $url, string $method, TableNode $headersTable):void { + $user = $this->featureContext->getActualUsername($user); + $authString = "$user:" . $this->featureContext->getPasswordForUser($user); + $url = $this->featureContext->substituteInLineCodes( + $url, + $user + ); + $this->featureContext->verifyTableNodeColumns( + $headersTable, + ['header', 'value'] + ); + $headers = []; + foreach ($headersTable as $row) { + $headers[$row['header']] = $row ['value']; + } + $this->sendRequest( + $url, + $method, + 'basic ' . \base64_encode($authString), + false, + null, + $headers + ); + } + + /** + * @Given user :user has requested :url with :method using basic auth + * + * @param string $user + * @param string $url + * @param string $method + * @param string|null $password + * @param string|null $body + * + * @return void + */ + public function userHasRequestedURLWithUsingBasicAuth( + string $user, + string $url, + string $method, + ?string $password = null, + ?string $body = null + ):void { + $this->userRequestsURLWithUsingBasicAuth( + $user, + $url, + $method, + $password, + $body + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the administrator requests :url with :method using basic auth + * + * @param string $url + * @param string $method + * @param string|null $password + * + * @return void + */ + public function administratorRequestsURLWithUsingBasicAuth(string $url, string $method, ?string $password = null):void { + $this->userRequestsURLWithUsingBasicAuth( + $this->featureContext->getAdminUsername(), + $url, + $method, + $password + ); + } + + /** + * @When user :user requests :url with :method using basic token auth + * + * @param string $user + * @param string $url + * @param string $method + * + * @return void + */ + public function userRequestsURLWithUsingBasicTokenAuth(string $user, string $url, string $method):void { + $user = $this->featureContext->getActualUsername($user); + $this->sendRequest( + $url, + $method, + 'basic ' . \base64_encode("$user:" . $this->clientToken) + ); + } + + /** + * @Given user :user has requested :url with :method using basic token auth + * + * @param string $user + * @param string $url + * @param string $method + * + * @return void + */ + public function userHasRequestedURLWithUsingBasicTokenAuth(string $user, string $url, string $method):void { + $this->userRequestsURLWithUsingBasicTokenAuth($user, $url, $method); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the user requests :url with :method using the generated client token + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userRequestsURLWithUsingAClientToken(string $url, string $method):void { + $this->sendRequest($url, $method, 'token ' . $this->clientToken); + } + + /** + * @Given the user has requested :url with :method using the generated client token + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userHasRequestedURLWithUsingAClientToken(string $url, string $method):void { + $this->userRequestsURLWithUsingAClientToken($url, $method); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the user requests :url with :method using the generated app password + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userRequestsURLWithUsingAppPassword(string $url, string $method):void { + $this->sendRequest($url, $method, 'token ' . $this->appToken); + } + + /** + * @When the user requests :url with :method using app password named :tokenName + * + * @param string $url + * @param string $method + * @param string $tokenName + * + * @return void + */ + public function theUserRequestsWithUsingAppPasswordNamed(string $url, string $method, string $tokenName):void { + $this->sendRequest($url, $method, 'token ' . $this->appTokens[$tokenName]['token']); + } + + /** + * @Given the user has requested :url with :method using the generated app password + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userHasRequestedURLWithUsingAppPassword(string $url, string $method):void { + $this->userRequestsURLWithUsingAppPassword($url, $method); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the user requests :url with :method using the browser session + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userRequestsURLWithBrowserSession(string $url, string $method):void { + $this->sendRequest($url, $method, null, true); + } + + /** + * @Given the user has requested :url with :method using the browser session + * + * @param string $url + * @param string $method + * + * @return void + */ + public function userHasRequestedURLWithBrowserSession(string $url, string $method):void { + $this->userRequestsURLWithBrowserSession($url, $method); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Given a new browser session for :user has been started + * + * @param string $user + * + * @return void + */ + public function aNewBrowserSessionForHasBeenStarted(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $loginUrl = $this->featureContext->getBaseUrl() . '/index.php/login'; + // Request a new session and extract CSRF token + $this->featureContext->setResponse( + HttpRequestHelper::get( + $loginUrl, + $this->featureContext->getStepLineRef(), + null, + null, + null, + null, + null, + $this->featureContext->getCookieJar() + ) + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + $this->featureContext->extractRequestTokenFromResponse($this->featureContext->getResponse()); + + // Login and extract new token + $body = [ + 'user' => $this->featureContext->getActualUsername($user), + 'password' => $this->featureContext->getPasswordForUser($user), + 'requesttoken' => $this->featureContext->getRequestToken() + ]; + $this->featureContext->setResponse( + HttpRequestHelper::post( + $loginUrl, + $this->featureContext->getStepLineRef(), + null, + null, + null, + $body, + null, + $this->featureContext->getCookieJar() + ) + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + $this->featureContext->extractRequestTokenFromResponse($this->featureContext->getResponse()); + } + + /** + * @Given a new browser session for the administrator has been started + * + * @return void + */ + public function aNewBrowserSessionForTheAdministratorHasBeenStarted():void { + $admin = $this->featureContext->getAdminUsername(); + $this->aNewBrowserSessionForHasBeenStarted($admin); + } + + /** + * @When /^the administrator (enforces|does not enforce)\s?token auth$/ + * @Given /^token auth has (not|)\s?been enforced$/ + * + * @param string $hasOrNot + * + * @return void + * @throws Exception + */ + public function tokenAuthHasBeenEnforced(string $hasOrNot):void { + $enforce = (($hasOrNot !== "not") && ($hasOrNot !== "does not enforce")); + if ($enforce) { + $value = 'true'; + } else { + $value = 'false'; + } + $occStatus = SetupHelper::setSystemConfig( + 'token_auth_enforced', + $value, + $this->featureContext->getStepLineRef(), + 'boolean' + ); + if ($occStatus['code'] !== "0") { + throw new \Exception("setSystemConfig token_auth_enforced returned error code " . $occStatus['code']); + } + + // Remember that we set this value, so it can be removed after the scenario + $this->tokenAuthHasBeenSet = true; + $this->tokenAuthHasBeenSetTo = $value; + } + + /** + * + * @return string + */ + public function generateAuthTokenForAdmin():string { + $this->aNewBrowserSessionForHasBeenStarted($this->featureContext->getAdminUsername()); + $this->userGeneratesNewAppPasswordNamed('acceptance-test ' . \microtime()); + return $this->appToken; + } + + /** + * delete token_auth_enforced if it was set in the scenario + * + * @AfterScenario + * + * @return void + * @throws Exception + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteTokenAuthEnforcedAfterScenario():void { + if ($this->tokenAuthHasBeenSet) { + if ($this->tokenAuthHasBeenSetTo === 'true') { + // Because token auth is enforced, we have to use a token + // (app password) as the password to send to the testing app + // so it will authenticate us. + $appTokenForOccCommand = $this->generateAuthTokenForAdmin(); + } else { + $appTokenForOccCommand = null; + } + SetupHelper::deleteSystemConfig( + 'token_auth_enforced', + $this->featureContext->getStepLineRef(), + null, + $appTokenForOccCommand, + null, + null + ); + $this->tokenAuthHasBeenSet = false; + $this->tokenAuthHasBeenSetTo = ''; + } + } +} diff --git a/tests/acceptance/features/bootstrap/CapabilitiesContext.php b/tests/acceptance/features/bootstrap/CapabilitiesContext.php new file mode 100644 index 00000000000..bfdb3699563 --- /dev/null +++ b/tests/acceptance/features/bootstrap/CapabilitiesContext.php @@ -0,0 +1,223 @@ + + * @author Sergio Bertolin + * @author Phillip Davis + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; + +require_once 'bootstrap.php'; + +/** + * Capabilities context. + */ +class CapabilitiesContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @Then the capabilities should contain + * + * @param TableNode|null $formData + * + * @return void + * @throws Exception + */ + public function checkCapabilitiesResponse(TableNode $formData):void { + $capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__); + $assertedSomething = false; + + $this->featureContext->verifyTableNodeColumns($formData, ['value', 'path_to_element', 'capability']); + + foreach ($formData->getHash() as $row) { + $row['value'] = $this->featureContext->substituteInLineCodes($row['value']); + Assert::assertEquals( + $row['value'] === "EMPTY" ? '' : $row['value'], + $this->featureContext->appConfigurationContext->getParameterValueFromXml( + $capabilitiesXML, + $row['capability'], + $row['path_to_element'] + ), + "Failed field {$row['capability']} {$row['path_to_element']}" + ); + $assertedSomething = true; + } + + Assert::assertTrue( + $assertedSomething, + 'there was nothing in the table of expected capabilities' + ); + } + + /** + * @Then the version data in the response should contain + * + * @param TableNode|null $formData + * + * @return void + * @throws Exception + */ + public function checkVersionResponse(TableNode $formData):void { + $versionXML = $this->featureContext->appConfigurationContext->getVersionXml(__METHOD__); + $assertedSomething = false; + + $this->featureContext->verifyTableNodeColumns($formData, ['name', 'value']); + + foreach ($formData->getHash() as $row) { + $row['value'] = $this->featureContext->substituteInLineCodes($row['value']); + $actualValue = $versionXML->{$row['name']}; + + Assert::assertEquals( + $row['value'] === "EMPTY" ? '' : $row['value'], + $actualValue, + "Failed field {$row['name']}" + ); + $assertedSomething = true; + } + + Assert::assertTrue( + $assertedSomething, + 'there was nothing in the table of expected version data' + ); + } + + /** + * @Then the major-minor-micro version data in the response should match the version string + * + * @return void + * @throws Exception + */ + public function checkVersionMajorMinorMicroResponse():void { + $versionXML = $this->featureContext->appConfigurationContext->getVersionXml(__METHOD__); + $versionString = (string) $versionXML->string; + // We expect that versionString will be in a format like "10.9.2 beta" or "10.9.2-alpha" or "10.9.2" + $result = \preg_match('/^[0-9]+\.[0-9]+\.[0-9]+/', $versionString, $matches); + Assert::assertSame( + 1, + $result, + __METHOD__ . " version string '$versionString' does not start with a semver version" + ); + // semVerParts should have an array with the 3 semver components of the version, e.g. "1", "9" and "2". + $semVerParts = \explode('.', $matches[0]); + $expectedMajor = $semVerParts[0]; + $expectedMinor = $semVerParts[1]; + $expectedMicro = $semVerParts[2]; + $actualMajor = (string) $versionXML->major; + $actualMinor = (string) $versionXML->minor; + $actualMicro = (string) $versionXML->micro; + Assert::assertSame( + $expectedMajor, + $actualMajor, + __METHOD__ . "'major' data item does not match with major version in string '$versionString'" + ); + Assert::assertSame( + $expectedMinor, + $actualMinor, + __METHOD__ . "'minor' data item does not match with minor version in string '$versionString'" + ); + Assert::assertSame( + $expectedMicro, + $actualMicro, + __METHOD__ . "'micro' data item does not match with micro (patch) version in string '$versionString'" + ); + } + + /** + * @Then the :pathToElement capability of files sharing app should be :value + * + * @param string $pathToElement + * @param string $value + * + * @return void + * @throws Exception + */ + public function theCapabilityOfFilesSharingAppShouldBe( + string $pathToElement, + string $value + ):void { + $this->featureContext->appConfigurationContext->userGetsCapabilitiesCheckResponse( + $this->featureContext->getCurrentUser() + ); + $capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__); + $actualValue = $this->featureContext->appConfigurationContext->getParameterValueFromXml( + $capabilitiesXML, + "files_sharing", + $pathToElement + ); + Assert::assertEquals( + $value === "EMPTY" ? '' : $value, + $actualValue, + "Expected {$pathToElement} capability of files sharing app to be {$value}, but got {$actualValue}" + ); + } + + /** + * @Then the capabilities should not contain + * + * @param TableNode|null $formData + * + * @return void + */ + public function theCapabilitiesShouldNotContain(TableNode $formData):void { + $capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__); + $assertedSomething = false; + + foreach ($formData->getHash() as $row) { + Assert::assertFalse( + $this->featureContext->appConfigurationContext->parameterValueExistsInXml( + $capabilitiesXML, + $row['capability'], + $row['path_to_element'] + ), + "Capability {$row['capability']} {$row['path_to_element']} exists but it should not exist" + ); + $assertedSomething = true; + } + + Assert::assertTrue( + $assertedSomething, + 'there was nothing in the table of not expected capabilities' + ); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/ChecksumContext.php b/tests/acceptance/features/bootstrap/ChecksumContext.php new file mode 100644 index 00000000000..cc7d528279e --- /dev/null +++ b/tests/acceptance/features/bootstrap/ChecksumContext.php @@ -0,0 +1,464 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use PHPUnit\Framework\Assert; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * Checksum functions + */ +class ChecksumContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @When user :user uploads file :source to :destination with checksum :checksum using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * @param string $checksum + * + * @return void + */ + public function userUploadsFileToWithChecksumUsingTheAPI( + string $user, + string $source, + string $destination, + string $checksum + ):void { + $file = \file_get_contents( + $this->featureContext->acceptanceTestsDirLocation() . $source + ); + $response = $this->featureContext->makeDavRequest( + $user, + 'PUT', + $destination, + ['OC-Checksum' => $checksum], + $file, + "files" + ); + $this->featureContext->setResponse($response); + } + + /** + * @Given user :user has uploaded file :source to :destination with checksum :checksum + * + * @param string $user + * @param string $source + * @param string $destination + * @param string $checksum + * + * @return void + */ + public function userHasUploadedFileToWithChecksumUsingTheAPI( + string $user, + string $source, + string $destination, + string $checksum + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->userUploadsFileToWithChecksumUsingTheAPI( + $user, + $source, + $destination, + $checksum + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When user :user uploads file with content :content and checksum :checksum to :destination using the WebDAV API + * + * @param string $user + * @param string $content + * @param string $checksum + * @param string $destination + * + * @return void + */ + public function userUploadsFileWithContentAndChecksumToUsingTheAPI( + string $user, + string $content, + string $checksum, + string $destination + ):void { + $response = $this->featureContext->makeDavRequest( + $user, + 'PUT', + $destination, + ['OC-Checksum' => $checksum], + $content, + "files" + ); + $this->featureContext->setResponse($response); + } + + /** + * @Given user :user has uploaded file with content :content and checksum :checksum to :destination + * + * @param string $user + * @param string $content + * @param string $checksum + * @param string $destination + * + * @return void + */ + public function userHasUploadedFileWithContentAndChecksumToUsingTheAPI( + string $user, + string $content, + string $checksum, + string $destination + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->userUploadsFileWithContentAndChecksumToUsingTheAPI( + $user, + $content, + $checksum, + $destination + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When user :user requests the checksum of :path via propfind + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userRequestsTheChecksumOfViaPropfind(string $user, string $path):void { + $user = $this->featureContext->getActualUsername($user); + $body = ' + + + + + '; + $password = $this->featureContext->getPasswordForUser($user); + $response = WebDavHelper::makeDavRequest( + $this->featureContext->getBaseUrl(), + $user, + $password, + 'PROPFIND', + $path, + null, + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion() + ); + $this->featureContext->setResponse($response); + } + + /** + * @Then the webdav checksum should match :expectedChecksum + * + * @param string $expectedChecksum + * + * @return void + * @throws Exception + */ + public function theWebdavChecksumShouldMatch(string $expectedChecksum):void { + $service = new Sabre\Xml\Service(); + $bodyContents = $this->featureContext->getResponse()->getBody()->getContents(); + $parsed = $service->parse($bodyContents); + + /* + * Fetch the checksum array + * The checksums are way down in the array: + * $checksums = $parsed[0]['value'][1]['value'][0]['value'][0]; + * And inside is the actual checksum string: + * $checksums['value'][0]['value'] + * The Asserts below check the existence of the expected key at every level + * of the nested array. This helps to see what happened if a test fails + * because the response structure is not as expected. + */ + + Assert::assertIsArray( + $parsed, + __METHOD__ . " could not parse response as XML. Expected parsed XML to be an array but found " . $bodyContents + ); + Assert::assertArrayHasKey( + 0, + $parsed, + __METHOD__ . " parsed XML does not have key 0" + ); + $parsed0 = $parsed[0]; + Assert::assertArrayHasKey( + 'value', + $parsed0, + __METHOD__ . " parsed XML parsed0 does not have key value" + ); + $parsed0Value = $parsed0['value']; + Assert::assertArrayHasKey( + 1, + $parsed0Value, + __METHOD__ . " parsed XML parsed0Value does not have key 1" + ); + $parsed0Value1 = $parsed0Value[1]; + Assert::assertArrayHasKey( + 'value', + $parsed0Value1, + __METHOD__ . " parsed XML parsed0Value1 does not have key value after key 1" + ); + $parsed0Value1Value = $parsed0Value1['value']; + Assert::assertArrayHasKey( + 0, + $parsed0Value1Value, + __METHOD__ . " parsed XML parsed0Value1Value does not have key 0" + ); + $parsed0Value1Value0 = $parsed0Value1Value[0]; + Assert::assertArrayHasKey( + 'value', + $parsed0Value1Value0, + __METHOD__ . " parsed XML parsed0Value1Value0 does not have key value" + ); + $parsed0Value1Value0Value = $parsed0Value1Value0['value']; + Assert::assertArrayHasKey( + 0, + $parsed0Value1Value0Value, + __METHOD__ . " parsed XML parsed0Value1Value0Value does not have key 0" + ); + $checksums = $parsed0Value1Value0Value[0]; + Assert::assertArrayHasKey( + 'value', + $checksums, + __METHOD__ . " parsed XML checksums does not have key value" + ); + $checksumsValue = $checksums['value']; + Assert::assertArrayHasKey( + 0, + $checksumsValue, + __METHOD__ . " parsed XML checksumsValue does not have key 0" + ); + $checksumsValue0 = $checksumsValue[0]; + Assert::assertArrayHasKey( + 'value', + $checksumsValue0, + __METHOD__ . " parsed XML checksumsValue0 does not have key value" + ); + $actualChecksum = $checksumsValue0['value']; + Assert::assertEquals( + $expectedChecksum, + $actualChecksum, + "Expected: webDav checksum should be {$expectedChecksum} but got {$actualChecksum}" + ); + } + + /** + * @Then as user :user the webdav checksum of :path via propfind should match :expectedChecksum + * + * @param string $user + * @param string $path + * @param string $expectedChecksum + * + * @return void + * @throws Exception + */ + public function theWebdavChecksumOfViaPropfindShouldMatch(string $user, string $path, string $expectedChecksum):void { + $user = $this->featureContext->getActualUsername($user); + $this->userRequestsTheChecksumOfViaPropfind($user, $path); + $this->theWebdavChecksumShouldMatch($expectedChecksum); + } + + /** + * @Then the header checksum should match :expectedChecksum + * + * @param string $expectedChecksum + * + * @return void + * @throws Exception + */ + public function theHeaderChecksumShouldMatch(string $expectedChecksum):void { + $headerChecksums + = $this->featureContext->getResponse()->getHeader('OC-Checksum'); + + Assert::assertIsArray( + $headerChecksums, + __METHOD__ . " getHeader('OC-Checksum') did not return an array" + ); + + Assert::assertNotEmpty( + $headerChecksums, + __METHOD__ . " getHeader('OC-Checksum') returned an empty array. No checksum header was found." + ); + + $checksumCount = \count($headerChecksums); + + Assert::assertTrue( + $checksumCount === 1, + __METHOD__ . " Expected 1 checksum in the header but found $checksumCount checksums" + ); + + $headerChecksum + = $headerChecksums[0]; + Assert::assertEquals( + $expectedChecksum, + $headerChecksum, + "Expected: header checksum should match {$expectedChecksum} but got {$headerChecksum}" + ); + } + + /** + * @Then the header checksum when user :arg1 downloads file :arg2 using the WebDAV API should match :arg3 + * + * @param string $user + * @param string $fileName + * @param string $expectedChecksum + * + * @return void + * @throws Exception + */ + public function theHeaderChecksumWhenUserDownloadsFileUsingTheWebdavApiShouldMatch(string $user, string $fileName, string $expectedChecksum):void { + $this->featureContext->userDownloadsFileUsingTheAPI($user, $fileName); + $this->theHeaderChecksumShouldMatch($expectedChecksum); + } + + /** + * @Then the webdav checksum should be empty + * + * @return void + * @throws Exception + */ + public function theWebdavChecksumShouldBeEmpty():void { + $service = new Sabre\Xml\Service(); + $parsed = $service->parse( + $this->featureContext->getResponse()->getBody()->getContents() + ); + + /* + * Fetch the checksum array + * Maybe we want to do this a bit cleaner ;) + */ + $status = $parsed[0]['value'][1]['value'][1]['value']; + $expectedStatus = 'HTTP/1.1 404 Not Found'; + Assert::assertEquals( + $expectedStatus, + $status, + "Expected status to be {$expectedStatus} but got {$status}" + ); + } + + /** + * @Then the OC-Checksum header should not be there + * + * @return void + * @throws Exception + */ + public function theOcChecksumHeaderShouldNotBeThere():void { + $isHeader = $this->featureContext->getResponse()->hasHeader('OC-Checksum'); + Assert::assertFalse( + $isHeader, + "Expected no checksum header but got " + . $this->featureContext->getResponse()->getHeader('OC-Checksum') + ); + } + + /** + * @When user :user uploads chunk file :num of :total with :data to :destination with checksum :expectedChecksum using the WebDAV API + * + * @param string $user + * @param int $num + * @param int $total + * @param string $data + * @param string $destination + * @param string $expectedChecksum + * + * @return void + */ + public function userUploadsChunkFileOfWithToWithChecksum( + string $user, + int $num, + int $total, + string $data, + string $destination, + string $expectedChecksum + ):void { + $user = $this->featureContext->getActualUsername($user); + $num -= 1; + $file = "$destination-chunking-42-$total-$num"; + $response = $this->featureContext->makeDavRequest( + $user, + 'PUT', + $file, + ['OC-Checksum' => $expectedChecksum, 'OC-Chunked' => '1'], + $data, + "files" + ); + $this->featureContext->setResponse($response); + } + + /** + * @Given user :user has uploaded chunk file :num of :total with :data to :destination with checksum :expectedChecksum + * + * @param string $user + * @param int $num + * @param int $total + * @param string $data + * @param string $destination + * @param string $expectedChecksum + * + * @return void + */ + public function userHasUploadedChunkFileOfWithToWithChecksum( + string $user, + int $num, + int $total, + string $data, + string $destination, + string $expectedChecksum + ):void { + $this->userUploadsChunkFileOfWithToWithChecksum( + $user, + $num, + $total, + $data, + $destination, + $expectedChecksum + ); + $this->featureContext->theHTTPStatusCodeShouldBeOr(201, 206); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/FavoritesContext.php b/tests/acceptance/features/bootstrap/FavoritesContext.php new file mode 100644 index 00000000000..1164dc26a8a --- /dev/null +++ b/tests/acceptance/features/bootstrap/FavoritesContext.php @@ -0,0 +1,361 @@ + + * @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use Psr\Http\Message\ResponseInterface; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * context containing favorites related API steps + */ +class FavoritesContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var WebDavPropertiesContext + */ + private $webDavPropertiesContext; + + /** + * @param string$user + * @param string $path + * + * @return void + */ + public function userFavoritesElement(string $user, string $path):void { + $response = $this->changeFavStateOfAnElement( + $user, + $path, + 1 + ); + $this->featureContext->setResponse($response); + } + + /** + * @When user :user favorites element :path using the WebDAV API + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userFavoritesElementUsingWebDavApi(string $user, string $path):void { + $this->userFavoritesElement($user, $path); + } + + /** + * @Given user :user has favorited element :path + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userHasFavoritedElementUsingWebDavApi(string $user, string $path):void { + $this->userFavoritesElement($user, $path); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the user favorites element :path using the WebDAV API + * + * @param string $path + * + * @return void + */ + public function theUserFavoritesElement(string $path):void { + $this->userFavoritesElement( + $this->featureContext->getCurrentUser(), + $path + ); + } + + /** + * @Given the user has favorited element :path + * + * @param string $path + * + * @return void + */ + public function theUserHasFavoritedElement(string $path):void { + $this->userFavoritesElement( + $this->featureContext->getCurrentUser(), + $path + ); + $this->featureContext->theHTTPStatusCodeShouldBe( + 207, + "Expected response status code to be 207 (Multi-status), but not found! " + ); + } + + /** + * @param $user + * @param $path + * + * @return void + */ + public function userUnfavoritesElement(string $user, string $path):void { + $response = $this->changeFavStateOfAnElement( + $user, + $path, + 0 + ); + $this->featureContext->setResponse($response); + } + + /** + * @When user :user unfavorites element :path using the WebDAV API + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userUnfavoritesElementUsingWebDavApi(string $user, string $path):void { + $this->userUnfavoritesElement($user, $path); + } + + /** + * @Given user :user has unfavorited element :path + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userHasUnfavoritedElementUsingWebDavApi(string $user, string $path):void { + $this->userUnfavoritesElement($user, $path); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^user "([^"]*)" should (not|)\s?have favorited the following elements$/ + * + * @param string $user + * @param string $shouldOrNot (not|) + * @param TableNode $expectedElements + * + * @return void + */ + public function checkFavoritedElements( + string $user, + string $shouldOrNot, + TableNode $expectedElements + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->userListsFavorites($user, null); + $this->featureContext->propfindResultShouldContainEntries( + $shouldOrNot, + $expectedElements, + $user + ); + } + + /** + * @When /^user "([^"]*)" lists the favorites and limits the result to ([\d*]) elements using the WebDAV API$/ + * + * @param string $user + * @param int|null $limit + * + * @return void + */ + public function userListsFavorites(string $user, ?int $limit = null):void { + $renamedUser = $this->featureContext->getActualUsername($user); + $baseUrl = $this->featureContext->getBaseUrl(); + $password = $this->featureContext->getPasswordForUser($user); + $body + = "\n" . + " \n" . + " \n" . + " 1\n"; + + if ($limit !== null) { + $body .= " \n" . + " $limit\n" . + " \n"; + } + + $body .= " "; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $renamedUser, + $password, + "REPORT", + "/", + null, + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion() + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $path + * + * @return void + */ + public function theUserUnfavoritesElement(string $path):void { + $this->userUnfavoritesElement( + $this->featureContext->getCurrentUser(), + $path + ); + } + + /** + * @When the user unfavorites element :path using the WebDAV API + * + * @param string $path + * + * @return void + */ + public function theUserUnfavoritesElementUsingWebDavApi(string $path):void { + $this->theUserUnfavoritesElement($path); + } + + /** + * @Given the user has unfavorited element :path + * + * @param string $path + * + * @return void + */ + public function theUserHasUnfavoritedElementUsingWebDavApi(string $path):void { + $this->theUserUnfavoritesElement($path); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should be favorited$/ + * + * @param string $user + * @param string $path + * @param integer $expectedValue 0|1 + * + * @return void + */ + public function asUserFileOrFolderShouldBeFavorited(string $user, string $path, int $expectedValue = 1):void { + $property = "oc:favorite"; + $this->webDavPropertiesContext->asUserFolderShouldContainAPropertyWithValue( + $user, + $path, + $property, + (string)$expectedValue + ); + } + + /** + * @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should not be favorited$/ + * + * @param string $user + * @param string $path + * + * @return void + */ + public function asUserFileShouldNotBeFavorited(string $user, string $path):void { + $this->asUserFileOrFolderShouldBeFavorited($user, $path, 0); + } + + /** + * @Then /^as the user (?:file|folder|entry) "([^"]*)" should be favorited$/ + * + * @param string $path + * @param integer $expectedValue 0|1 + * + * @return void + */ + public function asTheUserFileOrFolderShouldBeFavorited(string $path, int $expectedValue = 1):void { + $this->asUserFileOrFolderShouldBeFavorited( + $this->featureContext->getCurrentUser(), + $path, + $expectedValue + ); + } + + /** + * @Then /^as the user (?:file|folder|entry) "([^"]*)" should not be favorited$/ + * + * @param string $path + * + * @return void + */ + public function asTheUserFileOrFolderShouldNotBeFavorited(string $path):void { + $this->asTheUserFileOrFolderShouldBeFavorited($path, 0); + } + + /** + * Set the elements of a proppatch + * + * @param string $user + * @param string $path + * @param int|null $favOrUnfav 1 = favorite, 0 = unfavorite + * + * @return ResponseInterface + */ + public function changeFavStateOfAnElement( + string $user, + string $path, + ?int $favOrUnfav + ):ResponseInterface { + $renamedUser = $this->featureContext->getActualUsername($user); + return WebDavHelper::proppatch( + $this->featureContext->getBaseUrl(), + $renamedUser, + $this->featureContext->getPasswordForUser($user), + $path, + 'favorite', + (string)$favOrUnfav, + $this->featureContext->getStepLineRef(), + "oc='http://owncloud.org/ns'", + $this->featureContext->getDavPathVersion() + ); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + $this->webDavPropertiesContext = $environment->getContext( + 'WebDavPropertiesContext' + ); + } +} diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php new file mode 100644 index 00000000000..b989863198a --- /dev/null +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -0,0 +1,4488 @@ + + * @author Phillip Davis + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Hook\Scope\BeforeStepScope; +use GuzzleHttp\Exception\GuzzleException; +use rdx\behatvars\BehatVariablesContext; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; +use Behat\Testwork\Hook\Scope\BeforeSuiteScope; +use GuzzleHttp\Cookie\CookieJar; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; +use TestHelpers\AppConfigHelper; +use TestHelpers\OcsApiHelper; +use TestHelpers\SetupHelper; +use TestHelpers\HttpRequestHelper; +use TestHelpers\UploadHelper; +use TestHelpers\OcisHelper; +use Laminas\Ldap\Ldap; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * Features context. + */ +class FeatureContext extends BehatVariablesContext { + use Provisioning; + use Sharing; + use WebDav; + + /** + * @var int Unix timestamp seconds + */ + private $scenarioStartTime; + + /** + * @var string + */ + private $adminUsername = ''; + + /** + * @var string + */ + private $adminPassword = ''; + + /** + * @var string + */ + private $adminDisplayName = ''; + + /** + * @var string + */ + private $adminEmailAddress = ''; + + /** + * @var string + */ + private $originalAdminPassword = ''; + + /** + * An array of values of replacement values of user attributes. + * These are only referenced when creating a user. After that, the + * run-time values are maintained and referenced in the $createdUsers array. + * + * Key is the username, value is an array of user attributes + * + * @var array|null + */ + private $userReplacements = null; + + /** + * @var string + */ + private $regularUserPassword = ''; + + /** + * @var string + */ + private $alt1UserPassword = ''; + + /** + * @var string + */ + private $alt2UserPassword = ''; + + /** + * @var string + */ + private $alt3UserPassword = ''; + + /** + * @var string + */ + private $alt4UserPassword = ''; + + /** + * The password to use in tests that create a sub-admin user + * + * @var string + */ + private $subAdminPassword = ''; + + /** + * The password to use in tests that create another admin user + * + * @var string + */ + private $alternateAdminPassword = ''; + + /** + * The password to use in tests that create public link shares + * + * @var string + */ + private $publicLinkSharePassword = ''; + + /** + * @var string + */ + private $ocPath = ''; + + /** + * @var string location of the root folder of ownCloud on the local server under test + */ + private $localServerRoot = null; + + /** + * @var string + */ + private $currentUser = ''; + + /** + * @var string + */ + private $currentServer = ''; + + /** + * The base URL of the current server under test, + * without any terminating slash + * e.g. http://localhost:8080 + * + * @var string + */ + private $baseUrl = ''; + + /** + * The base URL of the local server under test, + * without any terminating slash + * e.g. http://localhost:8080 + * + * @var string + */ + private $localBaseUrl = ''; + + /** + * The base URL of the remote (federated) server under test, + * without any terminating slash + * e.g. http://localhost:8180 + * + * @var string + */ + private $remoteBaseUrl = ''; + + /** + * The suite name, feature name and scenario line number. + * Example: apiComments/createComments.feature:24 + * + * @var string + */ + private $scenarioString = ''; + + /** + * A full unique reference to the step that is currently executing. + * Example: apiComments/createComments.feature:24-28 + * That is line 28, in the scenario at line 24, in the createComments feature + * in the apiComments suite. + * + * @var string + */ + private $stepLineRef = ''; + + /** + * @var bool|null + */ + private $sendStepLineRef = null; + + /** + * + * + * @var boolean true if TEST_SERVER_FED_URL is defined + */ + private $federatedServerExists = false; + + /** + * @var int + */ + private $ocsApiVersion = 1; + + /** + * @var ResponseInterface + */ + private $response = null; + + /** + * @var string + */ + private $responseUser = ""; + + /** + * @var string + */ + private $responseBodyContent = null; + + /** + * @var array + */ + private $userResponseBodyContents = []; + + /** + * @var array + */ + public $emailRecipients = []; + + /** + * @var CookieJar + */ + private $cookieJar; + + /** + * @var string + */ + private $requestToken; + + /** + * @var array + */ + private $storageIds = []; + + /** + * @var array + */ + private $createdFiles = []; + + /** + * The local source IP address from which to initiate API actions. + * Defaults to system-selected address matching IP address family and scope. + * + * @var string|null + */ + private $sourceIpAddress = null; + + private $guzzleClientHeaders = []; + + /** + * + * @var OCSContext + */ + public $ocsContext; + + /** + * + * @var AuthContext + */ + public $authContext; + + /** + * + * @var GraphContext + */ + public $graphContext; + + /** + * + * @var AppConfigurationContext + */ + public $appConfigurationContext; + + /** + * @var array saved configuration of the system before test runs as reported + * by occ config:list + */ + private $savedConfigList = []; + + /** + * @var array + */ + private $initialTrustedServer; + + /** + * @var int return code of last command + */ + private $occLastCode; + /** + * @var string stdout of last command + */ + private $lastStdOut; + /** + * @var string stderr of last command + */ + private $lastStdErr; + /** + * The codes are stored as strings, even though they are numbers + * + * @var array last http status codes + */ + private $lastHttpStatusCodesArray = []; + /** + * @var array last ocs status codes + */ + private $lastOCSStatusCodesArray = []; + + /** + * @var bool + * + * this is set true for db conversion tests + */ + private $dbConversion = false; + + /** + * @param bool $value + * + * @return void + */ + public function setDbConversionState(bool $value): void { + $this->dbConversion = $value; + } + + /** + * @return bool + */ + public function isRunningForDbConversion(): bool { + return $this->dbConversion; + } + + /** + * @var string + */ + private $oCSelector; + + /** + * @param string $selector + * + * @return void + */ + public function setOCSelector(string $selector): void { + $this->oCSelector = $selector; + } + + /** + * @return string + */ + public function getOCSelector(): string { + return $this->oCSelector; + } + + /** + * @return void + */ + public function resetOccLastCode(): void { + $this->occLastCode = null; + } + + /** + * @param int $statusCode + * + * @return void + */ + public function setOccLastCode(?int $statusCode = null): void { + $this->occLastCode = $statusCode; + } + + /** + * @param string|null $httpStatusCode + * + * @return void + */ + public function pushToLastHttpStatusCodesArray(?string $httpStatusCode = null): void { + if ($httpStatusCode !== null) { + $this->lastHttpStatusCodesArray[] = $httpStatusCode; + } elseif ($this->getResponse()->getStatusCode() !== null) { + $this->lastHttpStatusCodesArray[] = (string)$this->getResponse()->getStatusCode(); + } + } + + /** + * @return void + */ + public function emptyLastHTTPStatusCodesArray(): void { + $this->lastHttpStatusCodesArray = []; + } + + /** + * @return void + */ + public function emptyLastOCSStatusCodesArray(): void { + $this->lastOCSStatusCodesArray = []; + } + + /** + * @return void + */ + public function clearStatusCodeArrays(): void { + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + } + + /** + * @param string $ocsStatusCode + * + * @return void + */ + public function pushToLastOcsCodesArray(string $ocsStatusCode): void { + array_push($this->lastOCSStatusCodesArray, $ocsStatusCode); + } + + /** + * Add HTTP and OCS status code of the last response to the respective status code array + * + * @return void + */ + public function pushToLastStatusCodesArrays(): void { + $this->pushToLastHttpStatusCodesArray( + (string)$this->getResponse()->getStatusCode() + ); + try { + $this->pushToLastOcsCodesArray( + $this->ocsContext->getOCSResponseStatusCode( + $this->getResponse() + ) + ); + } catch (Exception $exception) { + // if response couldn't be converted into xml then push "notset" to last ocs status codes array + $this->pushToLastOcsCodesArray("notset"); + } + } + + /** + * @param string $emailAddress + * + * @return void + */ + public function pushEmailRecipientAsMailBox(string $emailAddress): void { + $mailBox = explode("@", $emailAddress)[0]; + if (!\in_array($mailBox, $this->emailRecipients)) { + $this->emailRecipients[] = $mailBox; + } + } + + /* + * @var Ldap + */ + private $ldap; + /** + * @var string + */ + private $ldapBaseDN; + /** + * @var string + */ + private $ldapHost; + /** + * @var int + */ + private $ldapPort; + /** + * @var string + */ + private $ldapAdminUser; + /** + * @var string + */ + private $ldapAdminPassword = ""; + /** + * @var string + */ + private $ldapUsersOU; + /** + * @var string + */ + private $ldapGroupsOU; + /** + * @var string + */ + private $ldapGroupSchema; + /** + * @var bool + */ + private $skipImportLdif; + /** + * @var array + */ + private $toDeleteDNs = []; + private $ldapCreatedUsers = []; + private $ldapCreatedGroups = []; + private $toDeleteLdapConfigs = []; + private $oldLdapConfig = []; + + /** + * @return Ldap + */ + public function getLdap(): Ldap { + return $this->ldap; + } + + /** + * @param string $configId + * + * @return void + */ + public function setToDeleteLdapConfigs(string $configId): void { + $this->toDeleteLdapConfigs[] = $configId; + } + + /** + * @return array + */ + public function getToDeleteLdapConfigs(): array { + return $this->toDeleteLdapConfigs; + } + + /** + * @param string $setValue + * + * @return void + */ + public function setToDeleteDNs(string $setValue): void { + $this->toDeleteDNs[] = $setValue; + } + + /** + * @return string + */ + public function getLdapBaseDN(): string { + return $this->ldapBaseDN; + } + + /** + * @return string + */ + public function getLdapUsersOU(): string { + return $this->ldapUsersOU; + } + + /** + * @return string + */ + public function getLdapGroupsOU(): string { + return $this->ldapGroupsOU; + } + + /** + * @return array + */ + public function getOldLdapConfig(): array { + return $this->oldLdapConfig; + } + + /** + * @param string $configId + * @param string $configKey + * @param string $value + * + * @return void + */ + public function setOldLdapConfig(string $configId, string $configKey, string $value): void { + $this->oldLdapConfig[$configId][$configKey] = $value; + } + + /** + * @return string + */ + public function getLdapHost(): string { + return $this->ldapHost; + } + + /** + * @return string + */ + public function getLdapHostWithoutScheme(): string { + return $this->removeSchemeFromUrl($this->ldapHost); + } + + /** + * @return integer + */ + public function getLdapPort(): int { + return $this->ldapPort; + } + + /** + * @return bool + */ + public function isTestingWithLdap(): bool { + return (\getenv("TEST_WITH_LDAP") === "true"); + } + + /** + * @return bool + */ + public function sendScenarioLineReferencesInXRequestId(): ?bool { + if ($this->sendStepLineRef === null) { + $this->sendStepLineRef = (\getenv("SEND_SCENARIO_LINE_REFERENCES") === "true"); + } + return $this->sendStepLineRef; + } + + /** + * @return bool + */ + public function isTestingReplacingUsernames(): bool { + return (\getenv('REPLACE_USERNAMES') === "true"); + } + + /** + * @return array|null + */ + public function usersToBeReplaced(): ?array { + if (($this->userReplacements === null) && $this->isTestingReplacingUsernames()) { + $this->userReplacements = \json_decode( + \file_get_contents("./tests/acceptance/usernames.json"), + true + ); + // Loop through the user replacements, and make entries for the lower + // and upper case forms. This allows for steps that specifically + // want to test that usernames like "alice", "Alice" and "ALICE" all work. + // Such steps will make useful replacements for each form. + foreach ($this->userReplacements as $key => $value) { + $lowerKey = \strtolower($key); + if ($lowerKey !== $key) { + $this->userReplacements[$lowerKey] = $value; + $this->userReplacements[$lowerKey]['username'] = \strtolower( + $this->userReplacements[$lowerKey]['username'] + ); + } + $upperKey = \strtoupper($key); + if ($upperKey !== $key) { + $this->userReplacements[$upperKey] = $value; + $this->userReplacements[$upperKey]['username'] = \strtoupper( + $this->userReplacements[$upperKey]['username'] + ); + } + } + } + return $this->userReplacements; + } + + /** + * BasicStructure constructor. + * + * @param string $baseUrl + * @param string $adminUsername + * @param string $adminPassword + * @param string $regularUserPassword + * @param string $ocPath + * + */ + public function __construct( + string $baseUrl, + string $adminUsername, + string $adminPassword, + string $regularUserPassword, + string $ocPath + ) { + // Initialize your context here + $this->baseUrl = \rtrim($baseUrl, '/'); + $this->adminUsername = $adminUsername; + $this->adminPassword = $adminPassword; + $this->regularUserPassword = $regularUserPassword; + $this->localBaseUrl = $this->baseUrl; + $this->currentServer = 'LOCAL'; + $this->cookieJar = new CookieJar(); + $this->ocPath = $ocPath; + + // PARALLEL DEPLOYMENT: ownCloud selector + $this->oCSelector = "oc10"; + + // These passwords are referenced in tests and can be overridden by + // setting environment variables. + $this->alt1UserPassword = "1234"; + $this->alt2UserPassword = "AaBb2Cc3Dd4"; + $this->alt3UserPassword = "aVeryLongPassword42TheMeaningOfLife"; + $this->alt4UserPassword = "ThisIsThe4thAlternatePwd"; + $this->subAdminPassword = "IamAJuniorAdmin42"; + $this->alternateAdminPassword = "IHave99LotsOfPriv"; + $this->publicLinkSharePassword = "publicPwd1"; + + // in case of CI deployment we take the server url from the environment + $testServerUrl = \getenv('TEST_SERVER_URL'); + if ($testServerUrl !== false) { + $this->baseUrl = \rtrim($testServerUrl, '/'); + $this->localBaseUrl = $this->baseUrl; + } + + // federated server url from the environment + $testRemoteServerUrl = \getenv('TEST_SERVER_FED_URL'); + if ($testRemoteServerUrl !== false) { + $this->remoteBaseUrl = \rtrim($testRemoteServerUrl, '/'); + $this->federatedServerExists = true; + } else { + $this->remoteBaseUrl = $this->localBaseUrl; + $this->federatedServerExists = false; + } + + // get the admin username from the environment (if defined) + $adminUsernameFromEnvironment = $this->getAdminUsernameFromEnvironment(); + if ($adminUsernameFromEnvironment !== false) { + $this->adminUsername = $adminUsernameFromEnvironment; + } + + // get the admin password from the environment (if defined) + $adminPasswordFromEnvironment = $this->getAdminPasswordFromEnvironment(); + if ($adminPasswordFromEnvironment !== false) { + $this->adminPassword = $adminPasswordFromEnvironment; + } + + // get the regular user password from the environment (if defined) + $regularUserPasswordFromEnvironment = $this->getRegularUserPasswordFromEnvironment(); + if ($regularUserPasswordFromEnvironment !== false) { + $this->regularUserPassword = $regularUserPasswordFromEnvironment; + } + + // get the alternate(1) user password from the environment (if defined) + $alt1UserPasswordFromEnvironment = $this->getAlt1UserPasswordFromEnvironment(); + if ($alt1UserPasswordFromEnvironment !== false) { + $this->alt1UserPassword = $alt1UserPasswordFromEnvironment; + } + + // get the alternate(2) user password from the environment (if defined) + $alt2UserPasswordFromEnvironment = $this->getAlt2UserPasswordFromEnvironment(); + if ($alt2UserPasswordFromEnvironment !== false) { + $this->alt2UserPassword = $alt2UserPasswordFromEnvironment; + } + + // get the alternate(3) user password from the environment (if defined) + $alt3UserPasswordFromEnvironment = $this->getAlt3UserPasswordFromEnvironment(); + if ($alt3UserPasswordFromEnvironment !== false) { + $this->alt3UserPassword = $alt3UserPasswordFromEnvironment; + } + + // get the alternate(4) user password from the environment (if defined) + $alt4UserPasswordFromEnvironment = $this->getAlt4UserPasswordFromEnvironment(); + if ($alt4UserPasswordFromEnvironment !== false) { + $this->alt4UserPassword = $alt4UserPasswordFromEnvironment; + } + + // get the sub-admin password from the environment (if defined) + $subAdminPasswordFromEnvironment = $this->getSubAdminPasswordFromEnvironment(); + if ($subAdminPasswordFromEnvironment !== false) { + $this->subAdminPassword = $subAdminPasswordFromEnvironment; + } + + // get the alternate admin password from the environment (if defined) + $alternateAdminPasswordFromEnvironment = $this->getAlternateAdminPasswordFromEnvironment(); + if ($alternateAdminPasswordFromEnvironment !== false) { + $this->alternateAdminPassword = $alternateAdminPasswordFromEnvironment; + } + + // get the public link share password from the environment (if defined) + $publicLinkSharePasswordFromEnvironment = $this->getPublicLinkSharePasswordFromEnvironment(); + if ($publicLinkSharePasswordFromEnvironment !== false) { + $this->publicLinkSharePassword = $publicLinkSharePasswordFromEnvironment; + } + $this->originalAdminPassword = $this->adminPassword; + } + + /** + * @param string $appTestCodeFullPath + * + * @return string the relative path from the core tests/acceptance dir + * to the equivalent dir in the app + */ + public function getPathFromCoreToAppAcceptanceTests( + string $appTestCodeFullPath + ): string { + // $appTestCodeFullPath is something like: + // '/somedir/anotherdir/core/apps/guests/tests/acceptance/features/bootstrap' + // and we want to know the 'apps/guests/tests/acceptance' part + + $path = \dirname($appTestCodeFullPath, 2); + $acceptanceDir = \basename($path); + $path = \dirname($path); + $testsDir = \basename($path); + $path = \dirname($path); + $appNameDir = \basename($path); + $path = \dirname($path); + // We specially are not sure about the name of the directory 'apps' + // Sometimes the app could be installed in some alternate apps directory + // like, for example, `apps-external`. So this really does need to be + // resolved here at run-time. + $appsDir = \basename($path); + // To get from core tests/acceptance we go up 2 levels then down through + // the above app dirs. + return "../../$appsDir/$appNameDir/$testsDir/$acceptanceDir"; + } + + /** + * Get the externally-defined admin username, if any + * + * @return string|false + */ + private static function getAdminUsernameFromEnvironment() { + return \getenv('ADMIN_USERNAME'); + } + + /** + * Get the externally-defined admin password, if any + * + * @return string|false + */ + private static function getAdminPasswordFromEnvironment() { + return \getenv('ADMIN_PASSWORD'); + } + + /** + * Get the externally-defined regular user password, if any + * + * @return string|false + */ + private static function getRegularUserPasswordFromEnvironment() { + return \getenv('REGULAR_USER_PASSWORD'); + } + + /** + * Get the externally-defined alternate(1) user password, if any + * + * @return string|false + */ + private static function getAlt1UserPasswordFromEnvironment() { + return \getenv('ALT1_USER_PASSWORD'); + } + + /** + * Get the externally-defined alternate(2) user password, if any + * + * @return string|false + */ + private static function getAlt2UserPasswordFromEnvironment() { + return \getenv('ALT2_USER_PASSWORD'); + } + + /** + * Get the externally-defined alternate(3) user password, if any + * + * @return string|false + */ + private static function getAlt3UserPasswordFromEnvironment() { + return \getenv('ALT3_USER_PASSWORD'); + } + + /** + * Get the externally-defined alternate(4) user password, if any + * + * @return string|false + */ + private static function getAlt4UserPasswordFromEnvironment() { + return \getenv('ALT4_USER_PASSWORD'); + } + + /** + * Get the externally-defined sub-admin password, if any + * + * @return string|false + */ + private static function getSubAdminPasswordFromEnvironment() { + return \getenv('SUB_ADMIN_PASSWORD'); + } + + /** + * Get the externally-defined alternate admin password, if any + * + * @return string|false + */ + private static function getAlternateAdminPasswordFromEnvironment() { + return \getenv('ALTERNATE_ADMIN_PASSWORD'); + } + + /** + * Get the externally-defined public link share password, if any + * + * @return string|false + */ + private static function getPublicLinkSharePasswordFromEnvironment() { + return \getenv('PUBLIC_LINK_SHARE_PASSWORD'); + } + + /** + * removes the scheme "http(s)://" (if any) from the front of a URL + * note: only needs to handle http or https + * + * @param string $url + * + * @return string + */ + public function removeSchemeFromUrl(string $url): string { + return \preg_replace( + "(^https?://)", + "", + $url + ); + } + + /** + * @return string + */ + public function getOcPath(): string { + return $this->ocPath; + } + + /** + * @return CookieJar + */ + public function getCookieJar(): CookieJar { + return $this->cookieJar; + } + + /** + * @return string + */ + public function getRequestToken(): string { + return $this->requestToken; + } + + /** + * returns the base URL (which is without a slash at the end) + * + * @return string + */ + public function getBaseUrl(): string { + return $this->baseUrl; + } + + /** + * returns the path of the base URL + * e.g. owncloud-core/10 if the baseUrl is http://localhost/owncloud-core/10 + * the path is without a slash at the end and without a slash at the beginning + * + * @return string + */ + public function getBasePath(): string { + $parsedUrl = \parse_url($this->getBaseUrl(), PHP_URL_PATH); + // If the server-under-test is at the "top" of the domain then parse_url returns null. + // For example, testing a server at http://localhost:8080 or http://example.com + if ($parsedUrl === null) { + $parsedUrl = ''; + } + return \ltrim($parsedUrl, "/"); + } + + /** + * returns the OCS path + * the path is without a slash at the end and without a slash at the beginning + * + * @param string $ocsApiVersion + * + * @return string + */ + public function getOCSPath(string $ocsApiVersion): string { + return \ltrim($this->getBasePath() . "/ocs/v$ocsApiVersion.php", "/"); + } + + /** + * returns the complete DAV path including the base path e.g. owncloud-core/remote.php/dav + * + * @return string + */ + public function getDAVPathIncludingBasePath(): string { + return \ltrim($this->getBasePath() . "/" . $this->getDavPath(), "/"); + } + + /** + * returns the base URL but without "http(s)://" in front of it + * + * @return string + */ + public function getBaseUrlWithoutScheme(): string { + return $this->removeSchemeFromUrl($this->getBaseUrl()); + } + + /** + * returns the local base URL (which is without a slash at the end) + * + * @return string + */ + public function getLocalBaseUrl(): string { + return $this->localBaseUrl; + } + + /** + * returns the local base URL but without "http(s)://" in front of it + * + * @return string + */ + public function getLocalBaseUrlWithoutScheme(): string { + return $this->removeSchemeFromUrl($this->getLocalBaseUrl()); + } + + /** + * returns the remote base URL (which is without a slash at the end) + * + * @return string + */ + public function getRemoteBaseUrl(): string { + return $this->remoteBaseUrl; + } + + /** + * returns the remote base URL but without "http(s)://" in front of it + * + * @return string + */ + public function getRemoteBaseUrlWithoutScheme(): string { + return $this->removeSchemeFromUrl($this->getRemoteBaseUrl()); + } + + /** + * returns the reference to the current line being executed. + * + * @return string + */ + public function getStepLineRef(): string { + if (!$this->sendStepLineRef) { + return ''; + } + + // If we are in BeforeScenario and possibly before any particular step + // is being executed, then stepLineRef might be empty. In that case + // return just the string for the scenario. + if ($this->stepLineRef === '') { + return $this->scenarioString; + } + return $this->stepLineRef; + } + + /** + * get the exit status of the last occ command + * app acceptance tests that have their own step code may need to process this + * + * @return int exit status code of the last occ command + */ + public function getExitStatusCodeOfOccCommand(): ?int { + return $this->occLastCode; + } + + /** + * get the normal output of the last occ command + * app acceptance tests that have their own step code may need to process this + * + * @return string normal output of the last occ command + */ + public function getStdOutOfOccCommand(): string { + return $this->lastStdOut; + } + + /** + * set the normal output of the last occ command + * + * @param string $stdOut + * + * @return void + */ + public function setStdOutOfOccCommand(string $stdOut): void { + $this->lastStdOut = $stdOut; + } + + /** + * get the error output of the last occ command + * app acceptance tests that have their own step code may need to process this + * + * @return string error output of the last occ command + */ + public function getStdErrOfOccCommand(): string { + return $this->lastStdErr; + } + + /** + * returns the base URL without any sub-path e.g. http://localhost:8080 + * of the base URL http://localhost:8080/owncloud + * + * @return string + */ + public function getBaseUrlWithoutPath(): string { + $parts = \parse_url($this->getBaseUrl()); + $url = $parts ["scheme"] . "://" . $parts["host"]; + if (isset($parts["port"])) { + $url = "$url:" . $parts["port"]; + } + return $url; + } + + /** + * @return int + */ + public function getOcsApiVersion(): int { + return $this->ocsApiVersion; + } + + /** + * @return string|null + */ + public function getSourceIpAddress(): ?string { + return $this->sourceIpAddress; + } + + /** + * @return array|null + */ + public function getStorageIds(): ?array { + return $this->storageIds; + } + + /** + * @param string $storageName + * + * @return integer + * @throws Exception + */ + public function getStorageId(string $storageName): int { + $storageIds = $this->getStorageIds(); + $storageId = \array_search($storageName, $storageIds); + Assert::assertNotFalse( + $storageId, + "Could not find storageId with storage name $storageName" + ); + return $storageId; + } + + /** + * @param string $storageName + * @param integer $storageId + * + * @return void + */ + public function addStorageId(string $storageName, int $storageId): void { + $this->storageIds[$storageId] = $storageName; + } + + /** + * @param integer $storageId + * + * @return void + */ + public function popStorageId(int $storageId): void { + unset($this->storageIds[$storageId]); + } + + /** + * @param string $sourceIpAddress + * + * @return void + */ + public function setSourceIpAddress(string $sourceIpAddress): void { + $this->sourceIpAddress = $sourceIpAddress; + } + + /** + * @return array + */ + public function getGuzzleClientHeaders(): array { + return $this->guzzleClientHeaders; + } + + /** + * @param array $guzzleClientHeaders ['X-Foo' => 'Bar'] + * + * @return void + */ + public function setGuzzleClientHeaders(array $guzzleClientHeaders): void { + $this->guzzleClientHeaders = $guzzleClientHeaders; + } + + /** + * @param array $guzzleClientHeaders ['X-Foo' => 'Bar'] + * + * @return void + */ + public function addGuzzleClientHeaders(array $guzzleClientHeaders): void { + $this->guzzleClientHeaders = \array_merge( + $this->guzzleClientHeaders, + $guzzleClientHeaders + ); + } + + /** + * @Given /^using OCS API version "([^"]*)"$/ + * + * @param string $version + * + * @return void + */ + public function usingOcsApiVersion(string $version): void { + $this->ocsApiVersion = (int)$version; + } + + /** + * @Given /^as user "([^"]*)"$/ + * + * @param string $user + * + * @return void + */ + public function asUser(string $user): void { + $this->currentUser = $this->getActualUsername($user); + } + + /** + * @Given as the administrator + * + * @return void + */ + public function asTheAdministrator(): void { + $this->currentUser = $this->getAdminUsername(); + } + + /** + * @return string + */ + public function getCurrentUser(): string { + return $this->currentUser; + } + + /** + * @param string $user + * + * @return void + */ + public function setCurrentUser(string $user): void { + $this->currentUser = $user; + } + + /** + * returns $this->response + * some steps use that private var to store the response for other steps + * + * @return ResponseInterface + */ + public function getResponse(): ?ResponseInterface { + return $this->response; + } + + /** + * let this class remember a response that was received elsewhere + * so that steps in this class can be used to examine the response + * + * @param ResponseInterface|null $response + * @param string $username of the user that received the response + * + * @return void + */ + public function setResponse( + ?ResponseInterface $response, + string $username = "" + ): void { + $this->response = $response; + //after a new response reset the response xml + $this->responseXml = []; + //after a new response reset the response xml object + $this->responseXmlObject = null; + // remember the user that received the response + $this->responseUser = $username; + } + + /** + * @return string + */ + public function getCurrentServer(): string { + return $this->currentServer; + } + + /** + * @Given /^using server "(LOCAL|REMOTE)"$/ + * + * @param string|null $server + * + * @return string Previous used server + */ + public function usingServer(?string $server): string { + $previousServer = $this->currentServer; + if ($server === 'LOCAL') { + $this->baseUrl = $this->localBaseUrl; + $this->currentServer = 'LOCAL'; + } else { + $this->baseUrl = $this->remoteBaseUrl; + $this->currentServer = 'REMOTE'; + } + return $previousServer; + } + + /** + * + * @return boolean + */ + public function federatedServerExists(): bool { + return $this->federatedServerExists; + } + + /** + * disable CSRF + * + * @return string the previous setting of csrf.disabled + * @throws Exception + */ + public function disableCSRF(): string { + return $this->setCSRFDotDisabled('true'); + } + + /** + * enable CSRF + * + * @return string the previous setting of csrf.disabled + * @throws Exception + */ + public function enableCSRF(): string { + return $this->setCSRFDotDisabled('false'); + } + + /** + * set csrf.disabled + * + * @param string $setting "true", "false" or "" to delete the setting + * + * @return string the previous setting of csrf.disabled + * @throws Exception + */ + public function setCSRFDotDisabled(string $setting): string { + $oldCSRFSetting = SetupHelper::getSystemConfigValue( + 'csrf.disabled', + $this->getStepLineRef() + ); + + if ($setting === "") { + SetupHelper::deleteSystemConfig( + 'csrf.disabled', + $this->getStepLineRef() + ); + } elseif (($setting === 'true') || ($setting === 'false')) { + SetupHelper::setSystemConfig( + 'csrf.disabled', + $setting, + $this->getStepLineRef(), + 'boolean' + ); + } else { + throw new \http\Exception\InvalidArgumentException( + 'setting must be "true", "false" or ""' + ); + } + return \trim($oldCSRFSetting); + } + + /** + * Parses the response as XML + * + * @param ResponseInterface|null $response + * @param string|null $exceptionText text to put at the front of exception messages + * + * @return SimpleXMLElement + * @throws Exception + */ + public function getResponseXml(?ResponseInterface $response = null, ?string $exceptionText = ''): SimpleXMLElement { + if ($response === null) { + $response = $this->response; + } + + if ($exceptionText === '') { + $exceptionText = __METHOD__; + } + return HttpRequestHelper::getResponseXml($response, $exceptionText); + } + + /** + * Parses the xml answer to get the requested key and sub-key + * + * @param ResponseInterface $response + * @param string $key1 + * @param string $key2 + * + * @return string + * @throws Exception + */ + public function getXMLKey1Key2Value(ResponseInterface $response, string $key1, string $key2): string { + return (string)$this->getResponseXml($response, __METHOD__)->$key1->$key2; + } + + /** + * Parses the xml answer to get the requested key sequence + * + * @param ResponseInterface $response + * @param string $key1 + * @param string $key2 + * @param string $key3 + * + * @return string + * @throws Exception + */ + public function getXMLKey1Key2Key3Value( + ResponseInterface $response, + string $key1, + string $key2, + string $key3 + ): string { + return (string)$this->getResponseXml($response, __METHOD__)->$key1->$key2->$key3; + } + + /** + * Parses the xml answer to get the requested attribute value + * + * @param ResponseInterface $response + * @param string $key1 + * @param string $key2 + * @param string $key3 + * @param string $attribute + * + * @return string + * @throws Exception + */ + public function getXMLKey1Key2Key3AttributeValue( + ResponseInterface $response, + string $key1, + string $key2, + string $key3, + string $attribute + ): string { + return (string)$this->getResponseXml($response, __METHOD__)->$key1->$key2->$key3->attributes()->$attribute; + } + + /** + * This function is needed to use a vertical fashion in the gherkin tables. + * + * @param array $arrayOfArrays + * + * @return array + */ + public function simplifyArray(array $arrayOfArrays): array { + $a = \array_map( + function ($subArray) { + return $subArray[0]; + }, + $arrayOfArrays + ); + return $a; + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to URL "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * + * @return void + */ + public function userSendsHTTPMethodToUrl(string $user, string $verb, string $url): void { + $user = $this->getActualUsername($user); + $this->sendingToWithDirectUrl($user, $verb, $url, null); + } + + /** + * @Given /^user "([^"]*)" has sent HTTP method "([^"]*)" to URL "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * + * @return void + */ + public function userHasSentHTTPMethodToUrl(string $user, string $verb, string $url): void { + $this->userSendsHTTPMethodToUrl($user, $verb, $url); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to URL "([^"]*)" with password "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string $password + * + * @return void + */ + public function userSendsHTTPMethodToUrlWithPassword(string $user, string $verb, string $url, string $password): void { + $this->sendingToWithDirectUrl($user, $verb, $url, null, $password); + } + + /** + * @Given /^user "([^"]*)" has sent HTTP method "([^"]*)" to URL "([^"]*)" with password "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string $password + * + * @return void + */ + public function userHasSentHTTPMethodToUrlWithPassword(string $user, string $verb, string $url, string $password): void { + $this->userSendsHTTPMethodToUrlWithPassword($user, $verb, $url, $password); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string|null $user + * @param string|null $verb + * @param string|null $url + * @param TableNode|null $body + * @param string|null $password + * + * @return void + * @throws GuzzleException + */ + public function sendingToWithDirectUrl(?string $user, ?string $verb, ?string $url, ?TableNode $body, ?string $password = null): void { + $fullUrl = $this->getBaseUrl() . $url; + + if ($password === null) { + $password = $this->getPasswordForUser($user); + } + + $headers = $this->guzzleClientHeaders; + + $config = null; + if ($this->sourceIpAddress !== null) { + $config = [ + 'curl' => [ + CURLOPT_INTERFACE => $this->sourceIpAddress + ] + ]; + } + + $cookies = null; + if (!empty($this->cookieJar->toArray())) { + $cookies = $this->cookieJar; + } + + $bodyRows = null; + if ($body instanceof TableNode) { + $bodyRows = $body->getRowsHash(); + } + + if (isset($this->requestToken)) { + $headers['requesttoken'] = $this->requestToken; + } + + $this->response = HttpRequestHelper::sendRequest( + $fullUrl, + $this->getStepLineRef(), + $verb, + $user, + $password, + $headers, + $bodyRows, + $config, + $cookies + ); + } + + /** + * @param string $url + * + * @return bool + */ + public function isAPublicLinkUrl(string $url): bool { + if (OcisHelper::isTestingOnReva()) { + $urlEnding = \ltrim($url, '/'); + } else { + if (\substr($url, 0, 4) !== "http") { + return false; + } + $urlEnding = \substr($url, \strlen($this->getBaseUrl() . '/')); + } + + if (OcisHelper::isTestingOnOcisOrReva()) { + $matchResult = \preg_match("%^(#/)?s/([a-zA-Z0-9]{15})$%", $urlEnding); + } else { + $matchResult = \preg_match("%^(index.php/)?s/([a-zA-Z0-9]{15})$%", $urlEnding); + } + + // preg_match returns (int) 1 for a match, we want to return a boolean. + if ($matchResult === 1) { + $isPublicLinkUrl = true; + } else { + $isPublicLinkUrl = false; + } + return $isPublicLinkUrl; + } + + /** + * Check that the status code in the saved response is the expected status + * code, or one of the expected status codes. + * + * @param int|int[]|string|string[] $expectedStatusCode + * @param string|null $message + * + * @return void + */ + public function theHTTPStatusCodeShouldBe($expectedStatusCode, ?string $message = ""): void { + $actualStatusCode = $this->response->getStatusCode(); + if (\is_array($expectedStatusCode)) { + if ($message === "") { + $message = "HTTP status code $actualStatusCode is not one of the expected values " . \implode(" or ", $expectedStatusCode); + } + + Assert::assertContainsEquals( + $actualStatusCode, + $expectedStatusCode, + $message + ); + } else { + if ($message === "") { + $message = "HTTP status code $actualStatusCode is not the expected value $expectedStatusCode"; + } + + Assert::assertEquals( + $expectedStatusCode, + $actualStatusCode, + $message + ); + } + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @Then /^the HTTP status code should be "([^"]*)"$/ + * + * @param int|string $statusCode + * + * @return void + */ + public function thenTheHTTPStatusCodeShouldBe($statusCode): void { + $this->theHTTPStatusCodeShouldBe($statusCode); + } + + /** + * @Then /^the HTTP status code should be "([^"]*)" or "([^"]*)"$/ + * + * @param int|string $statusCode1 + * @param int|string $statusCode2 + * + * @return void + */ + public function theHTTPStatusCodeShouldBeOr($statusCode1, $statusCode2): void { + $this->theHTTPStatusCodeShouldBe( + [$statusCode1, $statusCode2] + ); + } + + /** + * @Then /^the HTTP status code should be between "(\d+)" and "(\d+)"$/ + * + * @param int|string $minStatusCode + * @param int|string $maxStatusCode + * + * @return void + */ + public function theHTTPStatusCodeShouldBeBetween( + $minStatusCode, + $maxStatusCode + ): void { + $statusCode = $this->response->getStatusCode(); + $message = "The HTTP status code $statusCode is not between $minStatusCode and $maxStatusCode"; + Assert::assertGreaterThanOrEqual( + $minStatusCode, + $statusCode, + $message + ); + Assert::assertLessThanOrEqual( + $maxStatusCode, + $statusCode, + $message + ); + } + + /** + * @Then the HTTP status code should be success + * + * @return void + */ + public function theHTTPStatusCodeShouldBeSuccess(): void { + $this->theHTTPStatusCodeShouldBeBetween(200, 299); + } + + /** + * @Then the HTTP status code should be failure + * + * @return void + */ + public function theHTTPStatusCodeShouldBeFailure(): void { + $statusCode = $this->response->getStatusCode(); + $message = "The HTTP status code $statusCode is not greater than or equals to 400"; + Assert::assertGreaterThanOrEqual( + 400, + $statusCode, + $message + ); + } + + /** + * + * @return bool + */ + public function theHTTPStatusCodeWasSuccess(): bool { + $statusCode = $this->response->getStatusCode(); + return (($statusCode >= 200) && ($statusCode <= 299)); + } + + /** + * Check the text in an HTTP responseXml message + * + * @Then /^the HTTP response message should be "([^"]*)"$/ + * + * @param string $expectedMessage + * + * @return void + * @throws Exception + */ + public function theHttpResponseMessageShouldBe(string $expectedMessage): void { + $actualMessage = $this->responseXml['value'][1]['value']; + Assert::assertEquals( + $expectedMessage, + $actualMessage, + "Expected $expectedMessage HTTP response message but got $actualMessage" + ); + } + + /** + * Check the text in an HTTP reason phrase + * + * @Then /^the HTTP reason phrase should be "([^"]*)"$/ + * + * @param string $reasonPhrase + * + * @return void + */ + public function theHTTPReasonPhraseShouldBe(string $reasonPhrase): void { + Assert::assertEquals( + $reasonPhrase, + $this->getResponse()->getReasonPhrase(), + 'Unexpected HTTP reason phrase in response' + ); + } + + /** + * Check the text in an HTTP reason phrase + * Use this step form if the expected text contains double quotes, + * single quotes and other content that theHTTPReasonPhraseShouldBe() + * cannot handle. + * + * After the step, write the expected text in PyString form like: + * + * """ + * File "abc.txt" can't be shared due to reason "xyz" + * """ + * + * @Then /^the HTTP reason phrase should be:$/ + * + * @param PyStringNode $reasonPhrase + * + * @return void + */ + public function theHTTPReasonPhraseShouldBePyString( + PyStringNode $reasonPhrase + ): void { + Assert::assertEquals( + $reasonPhrase->getRaw(), + $this->getResponse()->getReasonPhrase(), + 'Unexpected HTTP reason phrase in response' + ); + } + + /** + * @Then /^the XML "([^"]*)" "([^"]*)" value should be "([^"]*)"$/ + * + * @param string $key1 + * @param string $key2 + * @param string $idText + * + * @return void + * @throws Exception + */ + public function theXMLKey1Key2ValueShouldBe(string $key1, string $key2, string $idText): void { + $actualValue = $this->getXMLKey1Key2Value($this->response, $key1, $key2); + Assert::assertEquals( + $idText, + $actualValue, + "Expected $idText but got " + . $actualValue + ); + } + + /** + * @Then /^the XML "([^"]*)" "([^"]*)" "([^"]*)" value should be "([^"]*)"$/ + * + * @param string $key1 + * @param string $key2 + * @param string $key3 + * @param string $idText + * + * @return void + * @throws Exception + */ + public function theXMLKey1Key2Key3ValueShouldBe( + string $key1, + string $key2, + string $key3, + string $idText + ) { + $actualValue = $this->getXMLKey1Key2Key3Value($this->response, $key1, $key2, $key3); + Assert::assertEquals( + $idText, + $actualValue, + "Expected $idText but got " + . $actualValue + ); + } + + /** + * @Then /^the XML "([^"]*)" "([^"]*)" "([^"]*)" "([^"]*)" attribute value should be a valid version string$/ + * + * @param string $key1 + * @param string $key2 + * @param string $key3 + * @param string $attribute + * + * @return void + * @throws Exception + */ + public function theXMLKey1Key2AttributeValueShouldBe( + string $key1, + string $key2, + string $key3, + string $attribute + ): void { + $value = $this->getXMLKey1Key2Key3AttributeValue( + $this->response, + $key1, + $key2, + $key3, + $attribute + ); + Assert::assertTrue( + \version_compare($value, '0.0.1') >= 0, + "attribute $attribute value $value is not a valid version string" + ); + } + + /** + * @param ResponseInterface $response + * + * @return void + */ + public function extractRequestTokenFromResponse(ResponseInterface $response): void { + $this->requestToken = \substr( + \preg_replace( + '/(.*)data-requesttoken="(.*)">(.*)/sm', + '\2', + $response->getBody()->getContents() + ), + 0, + 89 + ); + } + + /** + * @Given /^user "([^"]*)" has logged in to a web-style session$/ + * + * @param string $user + * + * @return void + * @throws GuzzleException + * @throws JsonException + */ + public function userHasLoggedInToAWebStyleSessionUsingTheAPI(string $user): void { + $user = $this->getActualUsername($user); + $loginUrl = $this->getBaseUrl() . '/login'; + // Request a new session and extract CSRF token + + $config = null; + if ($this->sourceIpAddress !== null) { + $config = [ + 'curl' => [ + CURLOPT_INTERFACE => $this->sourceIpAddress + ] + ]; + } + + $this->response = HttpRequestHelper::get( + $loginUrl, + $this->getStepLineRef(), + null, + null, + $this->guzzleClientHeaders, + null, + $config, + $this->cookieJar + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + $this->extractRequestTokenFromResponse($this->response); + + // Login and extract new token + $password = $this->getPasswordForUser($user); + $body = [ + 'user' => $user, + 'password' => $password, + 'requesttoken' => $this->requestToken + ]; + $this->response = HttpRequestHelper::post( + $loginUrl, + $this->getStepLineRef(), + null, + null, + $this->guzzleClientHeaders, + $body, + $config, + $this->cookieJar + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + $this->extractRequestTokenFromResponse($this->response); + } + + /** + * @When the client sends a :method to :url of user :user with requesttoken + * + * @param string $method + * @param string $url + * @param string $user + * + * @return void + * @throws GuzzleException + * @throws JsonException + */ + public function sendingAToWithRequesttoken( + string $method, + string $url, + string $user + ): void { + $headers = $this->guzzleClientHeaders; + + $config = null; + if ($this->sourceIpAddress !== null) { + $config = [ + 'curl' => [ + CURLOPT_INTERFACE => $this->sourceIpAddress + ] + ]; + } + + $headers['requesttoken'] = $this->requestToken; + + $user = \strtolower($this->getActualUsername($user)); + $url = $this->getBaseUrl() . $url; + $url = $this->substituteInLineCodes($url, $user); + $this->response = HttpRequestHelper::sendRequest( + $url, + $this->getStepLineRef(), + $method, + null, + null, + $headers, + null, + $config, + $this->cookieJar + ); + } + + /** + * @Given the client has sent a :method to :url of user :user with requesttoken + * + * @param string $method + * @param string $url + * @param string $user + * + * @return void + */ + public function theClientHasSentAToWithRequesttoken( + string $method, + string $url, + string $user + ): void { + $this->sendingAToWithRequesttoken($method, $url, $user); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the client sends a :method to :url of user :user without requesttoken + * + * @param string $method + * @param string $url + * @param string|null $user + * + * @return void + * @throws JsonException + */ + public function sendingAToWithoutRequesttoken(string $method, string $url, ?string $user = null): void { + $config = null; + if ($this->sourceIpAddress !== null) { + $config = [ + 'curl' => [ + CURLOPT_INTERFACE => $this->sourceIpAddress + ] + ]; + } + + $user = \strtolower($this->getActualUsername($user)); + $url = $this->getBaseUrl() . $url; + $url = $this->substituteInLineCodes($url, $user); + $this->response = HttpRequestHelper::sendRequest( + $url, + $this->getStepLineRef(), + $method, + null, + null, + $this->guzzleClientHeaders, + null, + $config, + $this->cookieJar + ); + } + + /** + * @Given the client has sent a :method to :url without requesttoken + * + * @param string $method + * @param string $url + * + * @return void + */ + public function theClientHasSentAToWithoutRequesttoken(string $method, string $url): void { + $this->sendingAToWithoutRequesttoken($method, $url); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $path + * @param string $filename + * + * @return void + */ + public static function removeFile(string $path, string $filename): void { + if (\file_exists("$path$filename")) { + \unlink("$path$filename"); + } + } + + /** + * Creates a file locally in the file system of the test runner + * The file will be available to upload to the server + * + * @param string $name + * @param string $size + * @param string $endData + * + * @return void + */ + public function createLocalFileOfSpecificSize(string $name, string $size, string $endData = 'a'): void { + $folder = $this->workStorageDirLocation(); + if (!\is_dir($folder)) { + \mkDir($folder); + } + $file = \fopen($folder . $name, 'w'); + \fseek($file, $size - \strlen($endData), SEEK_CUR); + \fwrite($file, $endData); // write the end data to force the file size + \fclose($file); + } + + /** + * Make a directory under the server root on the ownCloud server + * + * @param string $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo' + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function mkDirOnServer(string $dirPathFromServerRoot): void { + SetupHelper::mkDirOnServer( + $dirPathFromServerRoot, + $this->getStepLineRef(), + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + } + + /** + * @param string $filePathFromServerRoot + * @param string $content + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function createFileOnServerWithContent( + string $filePathFromServerRoot, + string $content + ): void { + SetupHelper::createFileOnServer( + $filePathFromServerRoot, + $content, + $this->getStepLineRef(), + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + } + + /** + * @Given file :filename with text :text has been created in local storage on the server + * + * @param string $filename + * @param string $text + * + * @return void + * @throws Exception + */ + public function fileHasBeenCreatedInLocalStorageWithText(string $filename, string $text): void { + $this->createFileOnServerWithContent( + LOCAL_STORAGE_DIR_ON_REMOTE_SERVER . "/$filename", + $text + ); + } + + /** + * @Given file :filename has been deleted from local storage on the server + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function fileHasBeenDeletedInLocalStorage(string $filename): void { + SetupHelper::deleteFileOnServer( + LOCAL_STORAGE_DIR_ON_REMOTE_SERVER . "/$filename", + $this->getStepLineRef(), + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + } + + /** + * @param string $user + * + * @return boolean + */ + public function isAdminUsername(string $user): bool { + return ($user === $this->getAdminUsername()); + } + + /** + * @return string + */ + public function getAdminUsername(): string { + return $this->adminUsername; + } + + /** + * @return string + */ + public function getAdminPassword(): string { + return $this->adminPassword; + } + + /** + * @param string $password + * + * @return void + */ + public function rememberNewAdminPassword(string $password): void { + $this->adminPassword = $password; + } + + /** + * @param string|null $userName + * + * @return string + */ + public function getPasswordForUser(?string $userName): string { + $userNameNormalized = $this->normalizeUsername($userName); + $username = $this->getActualUsername($userNameNormalized); + if ($username === $this->getAdminUsername()) { + return $this->getAdminPassword(); + } elseif (\array_key_exists($username, $this->createdUsers)) { + return (string)$this->createdUsers[$username]['password']; + } elseif (\array_key_exists($username, $this->createdRemoteUsers)) { + return (string)$this->createdRemoteUsers[$username]['password']; + } + + // The user has not been created yet, see if there is a replacement + // defined for the user. + $usernameReplacements = $this->usersToBeReplaced(); + if (isset($usernameReplacements)) { + if (isset($usernameReplacements[$userNameNormalized])) { + return $usernameReplacements[$userNameNormalized]['password']; + } + } + + // Fall back to the default password used for the well-known users. + if ($username === 'regularuser') { + return $this->regularUserPassword; + } elseif ($username === 'alice') { + return $this->regularUserPassword; + } elseif ($username === 'brian') { + return $this->alt1UserPassword; + } elseif ($username === 'carol') { + return $this->alt2UserPassword; + } elseif ($username === 'david') { + return $this->alt3UserPassword; + } elseif ($username === 'emily') { + return $this->alt4UserPassword; + } elseif ($username === 'usergrp') { + return $this->regularUserPassword; + } elseif ($username === 'sharee1') { + return $this->regularUserPassword; + } + + // The user has not been created yet and is not one of the pre-known + // users. So let the caller have the default password. + return (string)$this->getActualPassword($this->regularUserPassword); + } + + /** + * Get the display name of the user. + * + * For users that have already been created, return their display name. + * For special known usernames, return the display name that is also used by LDAP tests. + * For other users, return null. They will not be assigned any particular + * display name by this function. + * + * @param string $userName + * + * @return string|null + */ + public function getDisplayNameForUser(string $userName): ?string { + $userNameNormalized = $this->normalizeUsername($userName); + $username = $this->getActualUsername($userNameNormalized); + if (\array_key_exists($username, $this->createdUsers)) { + if (isset($this->createdUsers[$username]['displayname'])) { + return (string)$this->createdUsers[$username]['displayname']; + } + return $userName; + } + if (\array_key_exists($username, $this->createdRemoteUsers)) { + if (isset($this->createdRemoteUsers[$username]['displayname'])) { + return (string)$this->createdRemoteUsers[$username]['displayname']; + } + return $userName; + } + + // The user has not been created yet, see if there is a replacement + // defined for the user. + $usernameReplacements = $this->usersToBeReplaced(); + if (isset($usernameReplacements)) { + if (isset($usernameReplacements[$userNameNormalized])) { + return $usernameReplacements[$userNameNormalized]['displayname']; + } elseif (isset($usernameReplacements[$userName])) { + return $usernameReplacements[$userName]['displayname']; + } + } + + // Fall back to the default display name used for the well-known users. + if ($username === 'regularuser') { + return 'Regular User'; + } elseif ($username === 'alice') { + return 'Alice Hansen'; + } elseif ($username === 'brian') { + return 'Brian Murphy'; + } elseif ($username === 'carol') { + return 'Carol King'; + } elseif ($username === 'david') { + return 'David Lopez'; + } elseif ($username === 'emily') { + return 'Emily Wagner'; + } elseif ($username === 'usergrp') { + return 'User Grp'; + } elseif ($username === 'sharee1') { + return 'Sharee One'; + } elseif ($username === 'sharee2') { + return 'Sharee Two'; + } elseif (\in_array($username, ["grp1", "***redacted***"])) { + return $username; + } + return null; + } + + /** + * Get the email address of the user. + * + * For users that have already been created, return their email address. + * For special known usernames, return the email address that is also used by LDAP tests. + * For other users, return null. They will not be assigned any particular + * email address by this function. + * + * @param string $userName + * + * @return string|null + */ + public function getEmailAddressForUser(string $userName): ?string { + $userNameNormalized = $this->normalizeUsername($userName); + $username = $this->getActualUsername($userNameNormalized); + if (\array_key_exists($username, $this->createdUsers)) { + return (string)$this->createdUsers[$username]['email']; + } + if (\array_key_exists($username, $this->createdRemoteUsers)) { + return (string)$this->createdRemoteUsers[$username]['email']; + } + + // The user has not been created yet, see if there is a replacement + // defined for the user. + $usernameReplacements = $this->usersToBeReplaced(); + if (isset($usernameReplacements)) { + if (isset($usernameReplacements[$userNameNormalized])) { + return $usernameReplacements[$userNameNormalized]['email']; + } elseif (isset($usernameReplacements[$userName])) { + return $usernameReplacements[$userName]['email']; + } + } + + // Fall back to the default display name used for the well-known users. + if ($username === 'regularuser') { + return 'regularuser@example.org'; + } elseif ($username === 'alice') { + return 'alice@example.org'; + } elseif ($username === 'brian') { + return 'brian@example.org'; + } elseif ($username === 'carol') { + return 'carol@example.org'; + } elseif ($username === 'david') { + return 'david@example.org'; + } elseif ($username === 'emily') { + return 'emily@example.org'; + } elseif ($username === 'usergrp') { + return 'usergrp@example.org'; + } elseif ($username === 'sharee1') { + return 'sharee1@example.org'; + } else { + return null; + } + } + + // TODO do similar for other usernames for e.g. %regularuser% or %test-user-1% + + /** + * @param string|null $functionalUsername + * + * @return string|null + * @throws JsonException + */ + public function getActualUsername(?string $functionalUsername): ?string { + if ($functionalUsername === null) { + return null; + } + $usernames = $this->usersToBeReplaced(); + if (isset($usernames)) { + if (isset($usernames[$functionalUsername])) { + return $usernames[$functionalUsername]['username']; + } + $normalizedUsername = $this->normalizeUsername($functionalUsername); + if (isset($usernames[$normalizedUsername])) { + return $usernames[$normalizedUsername]['username']; + } + } + if ($functionalUsername === "%admin%") { + return $this->getAdminUsername(); + } + return $functionalUsername; + } + + /** + * @param string|null $functionalPassword + * + * @return string|null + */ + public function getActualPassword(?string $functionalPassword): ?string { + if ($functionalPassword === "%regular%") { + return $this->regularUserPassword; + } elseif ($functionalPassword === "%alt1%") { + return $this->alt1UserPassword; + } elseif ($functionalPassword === "%alt2%") { + return $this->alt2UserPassword; + } elseif ($functionalPassword === "%alt3%") { + return $this->alt3UserPassword; + } elseif ($functionalPassword === "%alt4%") { + return $this->alt4UserPassword; + } elseif ($functionalPassword === "%subadmin%") { + return $this->subAdminPassword; + } elseif ($functionalPassword === "%admin%") { + return $this->getAdminPassword(); + } elseif ($functionalPassword === "%altadmin%") { + return $this->alternateAdminPassword; + } elseif ($functionalPassword === "%public%") { + return $this->publicLinkSharePassword; + } elseif ($functionalPassword === "%remove%") { + return ""; + } else { + return $functionalPassword; + } + } + + /** + * @param string $userName + * + * @return array + */ + public function getAuthOptionForUser(string $userName): array { + return [$userName, $this->getPasswordForUser($userName)]; + } + + /** + * @return array + */ + public function getAuthOptionForAdmin(): array { + return $this->getAuthOptionForUser($this->getAdminUsername()); + } + + /** + * @When the administrator requests status.php + * + * @return void + */ + public function theAdministratorRequestsStatusPhp(): void { + $this->response = $this->getStatusPhp(); + } + + /** + * @When the administrator creates file :path with content :content in local storage using the testing API + * + * @param string $path + * @param string $content + * + * @return void + */ + public function theAdministratorCreatesFileUsingTheTestingApi(string $path, string $content): void { + $this->theAdministratorCreatesFileWithContentInLocalStorageUsingTheTestingApi( + $path, + $content, + 'local_storage' + ); + } + + /** + * @Given the administrator has created file :path with content :content in local storage using the testing API + * + * @param string $path + * @param string $content + * + * @return void + */ + public function theAdministratorHasCreatedFileUsingTheTestingApi(string $path, string $content): void { + $this->theAdministratorHasCreatedFileWithContentInLocalStorageUsingTheTestingApi( + $path, + $content, + 'local_storage' + ); + } + + /** + * @When the administrator creates file :path with content :content in local storage :mountPoint using the testing API + * + * @param string $path + * @param string $content + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesFileWithContentInLocalStorageUsingTheTestingApi( + string $path, + string $content, + string $mountPoint + ): void { + $response = $this->copyContentToFileInTemporaryStorageOnSystemUnderTest( + "$mountPoint/$path", + $content + ); + $this->setResponse($response); + } + + /** + * @Given the administrator has created a file :path in temporary storage with the last exported content using the testing API + * + * @param string $path + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCreatedAFileInTemporaryStorageWithLastExportedContent( + string $path + ): void { + $commandOutput = $this->getStdOutOfOccCommand(); + $this->copyContentToFileInTemporaryStorageOnSystemUnderTest($path, $commandOutput); + $this->theFileWithContentShouldExistInTheServerRoot( + TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$path", + $commandOutput + ); + } + + /** + * @Given the administrator has created file :path with content :content in local storage :mountPoint + * + * @param string $path + * @param string $content + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCreatedFileWithContentInLocalStorageUsingTheTestingApi( + string $path, + string $content, + string $mountPoint + ): void { + $this->theAdministratorCreatesFileWithContentInLocalStorageUsingTheTestingApi( + $path, + $content, + $mountPoint + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * Copy a file from the test-runner to the temporary storage directory on + * the system-under-test. This uses the testing app to push the file into + * the backend of the server, where it can be seen by occ commands done in + * the server-under-test. + * + * @Given the administrator has copied file :localPath to :destination in temporary storage on the system under test + * + * @param string $localPath relative to the core "root" folder + * @param string $destination + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCopiedFileToTemporaryStorageOnTheSystemUnderTest( + string $localPath, + string $destination + ): void { + // FeatureContext is in tests/acceptance/features/bootstrap so go up 4 + // levels to the test-runner root + $testRunnerRoot = \dirname(__DIR__, 4); + // The local path is specified down from the root - e.g. tests/data/file.txt + $content = \file_get_contents("$testRunnerRoot/$localPath"); + Assert::assertNotFalse( + $content, + "Local file $localPath cannot be read" + ); + $this->copyContentToFileInTemporaryStorageOnSystemUnderTest($destination, $content); + $this->theFileWithContentShouldExistInTheServerRoot(TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$destination", $content); + } + + /** + * @param string $destination + * @param string $content + * + * @return ResponseInterface + * @throws Exception + */ + public function copyContentToFileInTemporaryStorageOnSystemUnderTest( + string $destination, + string $content + ): ResponseInterface { + $this->mkDirOnServer(TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER); + + return OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + 'POST', + "/apps/testing/api/v1/file", + $this->getStepLineRef(), + [ + 'file' => TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$destination", + 'content' => $content + ], + $this->getOcsApiVersion() + ); + } + + /** + * @When the administrator deletes file :path in local storage using the testing API + * + * @param string $path + * + * @return void + */ + public function theAdministratorDeletesFileInLocalStorageUsingTheTestingApi(string $path): void { + $user = $this->getAdminUsername(); + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getAdminPassword(), + 'DELETE', + "/apps/testing/api/v1/file", + $this->getStepLineRef(), + ['file' => LOCAL_STORAGE_DIR_ON_REMOTE_SERVER . "/$path"], + $this->getOcsApiVersion() + ); + $this->setResponse($response); + } + + /** + * @Given a file with the size of :size bytes and the name :name has been created locally + * + * @param int $size if not int given it will be cast to int + * @param string $name + * + * @return void + * @throws InvalidArgumentException + */ + public function aFileWithSizeAndNameHasBeenCreatedLocally(int $size, string $name): void { + $fullPath = UploadHelper::getUploadFilesDir($name); + if (\file_exists($fullPath)) { + throw new InvalidArgumentException( + __METHOD__ . " could not create '$fullPath' file exists" + ); + } + UploadHelper::createFileSpecificSize($fullPath, $size); + $this->createdFiles[] = $fullPath; + } + + /** + * + * @return ResponseInterface + */ + public function getStatusPhp(): ResponseInterface { + $fullUrl = $this->getBaseUrl() . "/status.php"; + + $config = null; + if ($this->sourceIpAddress !== null) { + $config = [ + 'curl' => [ + CURLOPT_INTERFACE => $this->sourceIpAddress + ] + ]; + } + + return HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->guzzleClientHeaders, + null, + $config + ); + } + + /** + * @Then the json responded should match with + * + * @param PyStringNode $jsonExpected + * + * @return void + */ + public function jsonRespondedShouldMatch(PyStringNode $jsonExpected): void { + $jsonExpectedEncoded = \json_encode($jsonExpected->getRaw()); + $jsonRespondedEncoded = \json_encode((string)$this->response->getBody()); + Assert::assertEquals( + $jsonExpectedEncoded, + $jsonRespondedEncoded, + "The json responded: $jsonRespondedEncoded does not match with json expected: $jsonExpectedEncoded" + ); + } + + /** + * @Then the status.php response should include + * + * @param PyStringNode $jsonExpected + * + * @return void + * @throws Exception + */ + public function statusPhpRespondedShouldMatch(PyStringNode $jsonExpected): void { + $jsonExpectedDecoded = \json_decode($jsonExpected->getRaw(), true); + $jsonRespondedDecoded = $this->getJsonDecodedResponse(); + + $this->appConfigurationContext->theAdministratorGetsCapabilitiesCheckResponse(); + $edition = $this->appConfigurationContext->getParameterValueFromXml( + $this->appConfigurationContext->getCapabilitiesXml(__METHOD__), + 'core', + 'status@@@edition' + ); + + if (!\strlen($edition)) { + Assert::fail( + "Cannot get edition from core capabilities" + ); + } + + $product = $this->appConfigurationContext->getParameterValueFromXml( + $this->appConfigurationContext->getCapabilitiesXml(__METHOD__), + 'core', + 'status@@@product' + ); + if (!\strlen($product)) { + Assert::fail( + "Cannot get product from core capabilities" + ); + } + + $productName = $this->appConfigurationContext->getParameterValueFromXml( + $this->appConfigurationContext->getCapabilitiesXml(__METHOD__), + 'core', + 'status@@@productname' + ); + + if (!\strlen($productName)) { + Assert::fail( + "Cannot get productname from core capabilities" + ); + } + + $jsonExpectedDecoded['edition'] = $edition; + $jsonExpectedDecoded['product'] = $product; + $jsonExpectedDecoded['productname'] = $productName; + + if (OcisHelper::isTestingOnOc10()) { + // On oC10 get the expected version values by parsing the output of "occ status" + $runOccStatus = $this->runOcc(['status']); + if ($runOccStatus === 0) { + $output = \explode("- ", $this->lastStdOut); + $version = \explode(": ", $output[3]); + Assert::assertEquals( + "version", + $version[0], + "Expected 'version' but got $version[0]" + ); + $versionString = \explode(": ", $output[4]); + Assert::assertEquals( + "versionstring", + $versionString[0], + "Expected 'versionstring' but got $versionString[0]" + ); + $jsonExpectedDecoded['version'] = \trim($version[1]); + $jsonExpectedDecoded['versionstring'] = \trim($versionString[1]); + } else { + Assert::fail( + "Cannot get version variables from occ - status $runOccStatus" + ); + } + } else { + // We are on oCIS or reva or some other implementation. We cannot do "occ status". + // So get the expected version values by looking in the capabilities response. + $version = $this->appConfigurationContext->getParameterValueFromXml( + $this->appConfigurationContext->getCapabilitiesXml(__METHOD__), + 'core', + 'status@@@version' + ); + + if (!\strlen($version)) { + Assert::fail( + "Cannot get version from core capabilities" + ); + } + + $versionString = $this->appConfigurationContext->getParameterValueFromXml( + $this->appConfigurationContext->getCapabilitiesXml(__METHOD__), + 'core', + 'status@@@versionstring' + ); + + if (!\strlen($versionString)) { + Assert::fail( + "Cannot get versionstring from core capabilities" + ); + } + + $jsonExpectedDecoded['version'] = $version; + $jsonExpectedDecoded['versionstring'] = $versionString; + } + $errorMessage = ""; + $errorFound = false; + foreach ($jsonExpectedDecoded as $key => $expectedValue) { + if (\array_key_exists($key, $jsonRespondedDecoded)) { + $actualValue = $jsonRespondedDecoded[$key]; + if ($actualValue !== $expectedValue) { + $errorMessage .= "$key expected value was $expectedValue but actual value was $actualValue\n"; + $errorFound = true; + } + } else { + $errorMessage .= "$key was not found in the status response\n"; + $errorFound = true; + } + } + Assert::assertFalse($errorFound, $errorMessage); + // We have checked that the status.php response has data that matches up with + // data found in the capabilities response and/or the "occ status" command output. + // But the output might be reported wrongly in all of these in the same way. + // So check that the values also seem "reasonable". + $version = $jsonExpectedDecoded['version']; + $versionString = $jsonExpectedDecoded['versionstring']; + Assert::assertMatchesRegularExpression( + "/^\d+\.\d+\.\d+\.\d+$/", + $version, + "version should be in a form like 10.9.8.1 but is $version" + ); + if (\preg_match("/^(\d+\.\d+\.\d+)\.\d+(-[0-9A-Za-z-]+)?(\+[0-9A-Za-z-]+)?$/", $version, $matches)) { + // We should have matched something like 10.9.8 - the first 3 numbers in the version. + // Ignore pre-releases and meta information + Assert::assertArrayHasKey( + 1, + $matches, + "version $version could not match the pattern Major.Minor.Patch" + ); + $majorMinorPatchVersion = $matches[1]; + } else { + Assert::fail("version '$version' does not start in a form like 10.9.8"); + } + Assert::assertStringStartsWith( + $majorMinorPatchVersion, + $versionString, + "versionstring should start with $majorMinorPatchVersion but is $versionString" + ); + } + + /** + * send request to read a server file for core + * + * @param string $path + * + * @return void + */ + public function readFileInServerRootForCore(string $path): void { + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + 'GET', + "/apps/testing/api/v1/file?file=$path", + $this->getStepLineRef() + ); + $this->setResponse($response); + } + + /** + * read a server file for ocis + * + * @param string $path + * + * @return string + * @throws Exception + */ + public function readFileInServerRootForOCIS(string $path): string { + $pathToOcis = \getenv("PATH_TO_OCIS"); + $targetFile = \rtrim($pathToOcis, "/") . "/" . "services/web/assets" . "/" . ltrim($path, '/'); + if (!\file_exists($targetFile)) { + throw new Exception('Target File ' . $targetFile . ' could not be found'); + } + return \file_get_contents($targetFile); + } + + /** + * send request to list a server file + * + * @param string $path + * + * @return void + */ + public function listTrashbinFileInServerRoot(string $path): void { + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + 'GET', + "/apps/testing/api/v1/dir?dir=$path", + $this->getStepLineRef() + ); + $this->setResponse($response); + } + + /** + * move file in server root + * + * @param string $path + * @param string $target + * + * @return void + */ + public function moveFileInServerRoot(string $path, string $target): void { + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + "MOVE", + "/apps/testing/api/v1/file", + $this->getStepLineRef(), + [ + 'source' => $path, + 'target' => $target + ] + ); + + $this->setResponse($response); + } + + /** + * @When the local storage mount for :mount is renamed to :target + * + * @param string $mount + * @param string $target + * + * @return void + */ + public function theLocalStorageMountForIsRenamedTo(string $mount, string $target): void { + $mountPath = TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/" . ltrim($mount, '/'); + $targetPath = TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/" . ltrim($target, '/'); + + $this->moveFileInServerRoot($mountPath, $targetPath); + } + + /** + * @Then the file :path with content :content should exist in the server root + * + * @param string $path + * @param string $content + * + * @return void + * @throws Exception + */ + public function theFileWithContentShouldExistInTheServerRoot(string $path, string $content): void { + if (OcisHelper::isTestingOnOcis()) { + $fileContent = $this->readFileInServerRootForOCIS($path); + } else { + $this->readFileInServerRootForCore($path); + $this->theHTTPStatusCodeShouldBe(200, 'Failed to read the file $path'); + $fileContent = $this->getResponseXml(); + $fileContent = (string)$fileContent->data->element->contentUrlEncoded; + $fileContent = \urldecode($fileContent); + } + Assert::assertSame( + $content, + $fileContent, + "The content of the file does not match with '$content'" + ); + } + + /** + * @Then /^the content in the response should match with the content of file "([^"]*)" in the server root$/ + * + * @param string $path + * + * @return void + * @throws Exception + */ + public function theContentInTheRespShouldMatchWithFileInTheServerRoot(string $path): void { + $content = $this->getResponse()->getBody()->getContents(); + $this->theFileWithContentShouldExistInTheServerRoot($path, $content); + } + + /** + * @Then the file :path should not exist in the server root + * + * @param string $path + * + * @return void + */ + public function theFileShouldNotExistInTheServerRoot(string $path): void { + $this->readFileInServerRootForCore($path); + Assert::assertSame( + 404, + $this->getResponse()->getStatusCode(), + "The file '$path' exists in the server root but was not expected to exist" + ); + } + + /** + * @Then the body of the response should be empty + * + * @return void + */ + public function theResponseBodyShouldBeEmpty(): void { + Assert::assertEmpty( + $this->getResponse()->getBody()->getContents(), + "The response body was expected to be empty but got " + . $this->getResponse()->getBody()->getContents() + ); + } + + /** + * @param ResponseInterface|null $response + * + * @return array + */ + public function getJsonDecodedResponse(?ResponseInterface $response = null): array { + if ($response === null) { + $response = $this->getResponse(); + } + return \json_decode( + (string)$response->getBody(), + true + ); + } + + /** + * + * @return array + */ + public function getJsonDecodedStatusPhp(): array { + return $this->getJsonDecodedResponse( + $this->getStatusPhp() + ); + } + + /** + * @return string + */ + public function getEditionFromStatus(): string { + $decodedResponse = $this->getJsonDecodedStatusPhp(); + if (isset($decodedResponse['edition'])) { + return $decodedResponse['edition']; + } + return ''; + } + + /** + * @return string|null + */ + public function getProductNameFromStatus(): ?string { + $decodedResponse = $this->getJsonDecodedStatusPhp(); + if (isset($decodedResponse['productname'])) { + return $decodedResponse['productname']; + } + return ''; + } + + /** + * @return string|null + */ + public function getVersionFromStatus(): ?string { + $decodedResponse = $this->getJsonDecodedStatusPhp(); + if (isset($decodedResponse['version'])) { + return $decodedResponse['version']; + } + return ''; + } + + /** + * @return string|null + */ + public function getVersionStringFromStatus(): ?string { + $decodedResponse = $this->getJsonDecodedStatusPhp(); + if (isset($decodedResponse['versionstring'])) { + return $decodedResponse['versionstring']; + } + return ''; + } + + /** + * returns a string that can be used to check a url of comments with + * regular expression (without delimiter) + * + * @return string + */ + public function getCommentUrlRegExp(): string { + $basePath = \ltrim($this->getBasePath() . "/", "/"); + return "/{$basePath}remote.php/dav/comments/files/([0-9]+)"; + } + + /** + * substitutes codes like %base_url% with the value + * if the given value does not have anything to be substituted + * then it is returned unmodified + * + * @param string|null $value + * @param string|null $user + * @param array|null $functions associative array of functions and parameters to be + * called on every replacement string before the + * replacement + * function name has to be the key and the parameters an + * own array + * the replacement itself will be used as first parameter + * e.g. substituteInLineCodes($value, ['preg_quote' => ['/']]) + * @param array|null $additionalSubstitutions + * array of additional substitution configurations + * [ + * [ + * "code" => "%my_code%", + * "function" => [ + * $myClass, + * "myFunction" + * ], + * "parameter" => [] + * ], + * ] + * + * @return string + */ + public function substituteInLineCodes( + ?string $value, + ?string $user = null, + ?array $functions = [], + ?array $additionalSubstitutions = [] + ): ?string { + $substitutions = [ + [ + "code" => "%base_url%", + "function" => [ + $this, + "getBaseUrl" + ], + "parameter" => [] + ], + [ + "code" => "%base_url_without_scheme%", + "function" => [ + $this, + "getBaseUrlWithoutScheme" + ], + "parameter" => [] + ], + [ + "code" => "%remote_server%", + "function" => [ + $this, + "getRemoteBaseUrl" + ], + "parameter" => [] + ], + [ + "code" => "%remote_server_without_scheme%", + "function" => [ + $this, + "getRemoteBaseUrlWithoutScheme" + ], + "parameter" => [] + ], + [ + "code" => "%local_server%", + "function" => [ + $this, + "getLocalBaseUrl" + ], + "parameter" => [] + ], + [ + "code" => "%local_server_without_scheme%", + "function" => [ + $this, + "getLocalBaseUrlWithoutScheme" + ], + "parameter" => [] + ], + [ + "code" => "%base_path%", + "function" => [ + $this, + "getBasePath" + ], + "parameter" => [] + ], + [ + "code" => "%dav_path%", + "function" => [ + $this, + "getDAVPathIncludingBasePath" + ], + "parameter" => [] + ], + [ + "code" => "%ocs_path_v1%", + "function" => [ + $this, + "getOCSPath" + ], + "parameter" => ["1"] + ], + [ + "code" => "%ocs_path_v2%", + "function" => [ + $this, + "getOCSPath" + ], + "parameter" => ["2"] + ], + [ + "code" => "%productname%", + "function" => [ + $this, + "getProductNameFromStatus" + ], + "parameter" => [] + ], + [ + "code" => "%edition%", + "function" => [ + $this, + "getEditionFromStatus" + ], + "parameter" => [] + ], + [ + "code" => "%version%", + "function" => [ + $this, + "getVersionFromStatus" + ], + "parameter" => [] + ], + [ + "code" => "%versionstring%", + "function" => [ + $this, + "getVersionStringFromStatus" + ], + "parameter" => [] + ], + [ + "code" => "%a_comment_url%", + "function" => [ + $this, + "getCommentUrlRegExp" + ], + "parameter" => [] + ], + [ + "code" => "%last_share_id%", + "function" => [ + $this, + "getLastShareId" + ], + "parameter" => [] + ], + [ + "code" => "%last_public_share_token%", + "function" => [ + $this, + "getLastPublicShareToken" + ], + "parameter" => [] + ] + ]; + if ($user !== null) { + array_push( + $substitutions, + [ + "code" => "%username%", + "function" => [ + $this, + "getActualUsername" + ], + "parameter" => [$user] + ], + [ + "code" => "%displayname%", + "function" => [ + $this, + "getDisplayNameForUser" + ], + "parameter" => [$user] + ], + [ + "code" => "%password%", + "function" => [ + $this, + "getPasswordForUser" + ], + "parameter" => [$user] + ], + [ + "code" => "%emailaddress%", + "function" => [ + $this, + "getEmailAddressForUser" + ], + "parameter" => [$user] + ], + [ + "code" => "%spaceid%", + "function" => [ + $this, + "getPersonalSpaceIdForUser", + ], + "parameter" => [$user, true] + ] + ); + } + + if (!empty($additionalSubstitutions)) { + $substitutions = \array_merge($substitutions, $additionalSubstitutions); + } + + foreach ($substitutions as $substitution) { + if (strpos($value, $substitution['code']) === false) { + continue; + } + + $replacement = \call_user_func_array( + $substitution["function"], + $substitution["parameter"] + ); + foreach ($functions as $function => $parameters) { + $replacement = \call_user_func_array( + $function, + \array_merge([$replacement], $parameters) + ); + } + $value = \str_replace( + $substitution["code"], + $replacement, + $value + ); + } + return $value; + } + + /** + * returns personal space id for user if the test is using the spaces dav path + * or if alwaysDoIt is set to true, + * otherwise it returns null. + * + * @param string $user + * @param bool $alwaysDoIt default false. Set to true + * + * @return string|null + * @throws GuzzleException + */ + public function getPersonalSpaceIdForUser(string $user, bool $alwaysDoIt = false): ?string { + if ($alwaysDoIt || ($this->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES)) { + return WebDavHelper::getPersonalSpaceIdForUserOrFakeIfNotFound( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + $this->getStepLineRef() + ); + } + return null; + } + + /** + * @return string + */ + public function temporaryStorageSubfolderName(): string { + return "work_tmp"; + } + + /** + * @return string + */ + public function acceptanceTestsDirLocation(): string { + return \dirname(__FILE__) . "/../../"; + } + + /** + * @return string + */ + public function workStorageDirLocation(): string { + return $this->acceptanceTestsDirLocation() . $this->temporaryStorageSubfolderName() . "/"; + } + + /** + * Get the path of the ownCloud server root directory + * + * @return string + * @throws Exception + */ + public function getServerRoot(): string { + if ($this->localServerRoot === null) { + $this->localServerRoot = SetupHelper::getServerRoot( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef() + ); + } + return $this->localServerRoot; + } + + /** + * @Then the config key :key of app :appID should have value :value + * + * @param string $key + * @param string $appID + * @param string $value + * + * @return void + * @throws Exception + */ + public function theConfigKeyOfAppShouldHaveValue(string $key, string $appID, string $value): void { + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + 'GET', + "/apps/testing/api/v1/app/$appID/$key", + $this->getStepLineRef(), + [], + $this->getOcsApiVersion() + ); + $configkeyValue = (string)$this->getResponseXml($response, __METHOD__)->data[0]->element->value; + Assert::assertEquals( + $value, + $configkeyValue, + "The config key $key of app $appID was expected to have value $value but got $configkeyValue" + ); + } + + /** + * Parse list of config keys from the given XML response + * + * @param SimpleXMLElement $responseXml + * + * @return array + */ + public function parseConfigListFromResponseXml(SimpleXMLElement $responseXml): array { + $configkeyData = \json_decode(\json_encode($responseXml->data), true); + if (isset($configkeyData['element'])) { + $configkeyData = $configkeyData['element']; + } else { + // There are no keys for the app + return []; + } + if (isset($configkeyData[0])) { + $configkeyValues = $configkeyData; + } else { + // There is just 1 key for the app + $configkeyValues[0] = $configkeyData; + } + return $configkeyValues; + } + + /** + * Returns a list of config keys for the given app + * + * @param string $appID + * @param string $exceptionText text to put at the front of exception messages + * + * @return array + * @throws Exception + */ + public function getConfigKeyList(string $appID, string $exceptionText = ''): array { + if ($exceptionText === '') { + $exceptionText = __METHOD__; + } + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + 'GET', + "/apps/testing/api/v1/app/$appID", + $this->getStepLineRef(), + [], + $this->getOcsApiVersion() + ); + return $this->parseConfigListFromResponseXml( + $this->getResponseXml($response, $exceptionText) + ); + } + + /** + * Check if given config key is present for given app + * + * @param string $key + * @param string $appID + * + * @return bool + * @throws Exception + */ + public function checkConfigKeyInApp(string $key, string $appID): bool { + $configkeyList = $this->getConfigKeyList($appID); + foreach ($configkeyList as $config) { + if ($config['configkey'] === $key) { + return true; + } + } + return false; + } + + /** + * @Then /^app ((?:'[^']*')|(?:"[^"]*")) should (not|)\s?have config key ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $appID + * @param string $shouldOrNot + * @param string $key + * + * @return void + * @throws Exception + */ + public function appShouldHaveConfigKey(string $appID, string $shouldOrNot, string $key): void { + $appID = \trim($appID, $appID[0]); + $key = \trim($key, $key[0]); + + $should = ($shouldOrNot !== "not"); + + if ($should) { + Assert::assertTrue( + $this->checkConfigKeyInApp($key, $appID), + "App $appID does not have config key $key" + ); + } else { + Assert::assertFalse( + $this->checkConfigKeyInApp($key, $appID), + "App $appID has config key $key but was not expected to" + ); + } + } + + /** + * @Then /^following config keys should (not|)\s?exist$/ + * + * @param string $shouldOrNot + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function followingConfigKeysShouldExist(string $shouldOrNot, TableNode $table): void { + $should = ($shouldOrNot !== "not"); + if ($should) { + foreach ($table as $item) { + Assert::assertTrue( + $this->checkConfigKeyInApp($item['configkey'], $item['appid']), + "{$item['appid']} was expected to have config key {$item['configkey']} but does not" + ); + } + } else { + foreach ($table as $item) { + Assert::assertFalse( + $this->checkConfigKeyInApp($item['configkey'], $item['appid']), + "Expected : {$item['appid']} should not have config key {$item['configkey']}" + ); + } + } + } + + /** + * @param string $user + * @param string|null $asUser + * @param string|null $password + * + * @return void + */ + public function sendUserSyncRequest(string $user, ?string $asUser = null, ?string $password = null): void { + $user = $this->getActualUsername($user); + $asUser = $asUser ?? $this->getAdminUsername(); + $password = $password ?? $this->getPasswordForUser($asUser); + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $asUser, + $password, + 'POST', + "/cloud/user-sync/$user", + $this->getStepLineRef(), + [], + $this->getOcsApiVersion() + ); + $this->setResponse($response); + } + + /** + * @When the administrator tries to sync user :user using the OCS API + * + * @param string $user + * + * @return void + */ + public function theAdministratorTriesToSyncUserUsingTheOcsApi(string $user): void { + $this->sendUserSyncRequest($user); + } + + /** + * @When user :asUser tries to sync user :user using the OCS API + * + * @param string $asUser + * @param string $user + * + * @return void + */ + public function userTriesToSyncUserUsingTheOcsApi(string $asUser, string $user): void { + $asUser = $this->getActualUsername($asUser); + $user = $this->getActualUsername($user); + $this->sendUserSyncRequest($user, $asUser); + } + + /** + * @When the administrator tries to sync user :user using password :password and the OCS API + * + * @param string|null $user + * @param string|null $password + * + * @return void + */ + public function theAdministratorTriesToSyncUserUsingPasswordAndTheOcsApi(?string $user, ?string $password): void { + $this->sendUserSyncRequest($user, null, $password); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + * @throws Exception + */ + public function before(BeforeScenarioScope $scope): void { + $this->scenarioStartTime = \time(); + // Get the environment + $environment = $scope->getEnvironment(); + // registers context in every suite, as every suite has FeatureContext + // that calls BasicStructure.php + $this->ocsContext = new OCSContext(); + $this->authContext = new AuthContext(); + $this->appConfigurationContext = new AppConfigurationContext(); + $this->ocsContext->before($scope); + $this->authContext->setUpScenario($scope); + $this->appConfigurationContext->setUpScenario($scope); + $environment->registerContext($this->ocsContext); + $environment->registerContext($this->authContext); + $environment->registerContext($this->appConfigurationContext); + $scenarioLine = $scope->getScenario()->getLine(); + $featureFile = $scope->getFeature()->getFile(); + $suiteName = $scope->getSuite()->getName(); + $featureFileName = \basename($featureFile); + + if ($this->sendScenarioLineReferencesInXRequestId()) { + $this->scenarioString = $suiteName . '/' . $featureFileName . ':' . $scenarioLine; + } else { + $this->scenarioString = ''; + } + + // Initialize SetupHelper + SetupHelper::init( + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getBaseUrl(), + $this->getOcPath() + ); + + if ($this->isTestingWithLdap()) { + $suiteParameters = SetupHelper::getSuiteParameters($scope); + $this->connectToLdap($suiteParameters); + } + + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext = new GraphContext(); + $this->graphContext->before($scope); + $environment->registerContext($this->graphContext); + } + } + + /** + * This will run before EVERY step. + * + * @BeforeStep + * + * @param BeforeStepScope $scope + * + * @return void + */ + public function beforeEachStep(BeforeStepScope $scope): void { + if ($this->sendScenarioLineReferencesInXRequestId()) { + $this->stepLineRef = $this->scenarioString . '-' . $scope->getStep()->getLine(); + } else { + $this->stepLineRef = ''; + } + } + + /** + * @BeforeScenario @local_storage + * + * @return void + * @throws Exception + */ + public function setupLocalStorageBefore(): void { + $storageName = "local_storage"; + $result = SetupHelper::createLocalStorageMount( + $storageName, + $this->getStepLineRef() + ); + $storageId = $result['storageId']; + if (!is_numeric($storageId)) { + throw new Exception( + __METHOD__ . " storageId '$storageId' is not numeric" + ); + } + $this->addStorageId($storageName, (int)$storageId); + SetupHelper::runOcc( + [ + 'files_external:option', + $storageId, + 'enable_sharing', + 'true' + ], + $this->getStepLineRef() + ); + } + + /** + * @AfterScenario + * + * @return void + */ + public function restoreAdminPassword(): void { + if ($this->adminPassword !== $this->originalAdminPassword) { + $this->resetUserPasswordAsAdminUsingTheProvisioningApi( + $this->getAdminUsername(), + $this->originalAdminPassword + ); + $this->adminPassword = $this->originalAdminPassword; + } + } + + /** + * @AfterScenario + * + * @return void + */ + public function deleteAllResourceCreatedByAdmin(): void { + foreach ($this->adminResources as $resource) { + $this->userDeletesFile("admin", $resource); + } + } + + /** + * deletes all created storages + * + * @return void + * @throws Exception + */ + public function deleteAllStorages(): void { + $allStorageIds = \array_keys($this->getStorageIds()); + foreach ($allStorageIds as $storageId) { + SetupHelper::runOcc( + [ + 'files_external:delete', + '-y', + $storageId + ], + $this->getStepLineRef() + ); + } + $this->storageIds = []; + } + + /** + * @AfterScenario @local_storage + * + * @return void + * @throws Exception + */ + public function removeLocalStorageAfter(): void { + $this->removeExternalStorage(); + $this->removeTemporaryStorageOnServerAfter(); + } + + /** + * This will remove test created external mount points + * + * @AfterScenario @external_storage + * + * @return void + * @throws Exception + */ + public function removeExternalStorage(): void { + if ($this->getStorageIds() !== null) { + $this->deleteAllStorages(); + } + } + + /** + * @BeforeScenario @temporary_storage_on_server + * + * @return void + * @throws Exception + */ + public function makeTemporaryStorageOnServerBefore(): void { + $this->mkDirOnServer( + TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER + ); + } + + /** + * @AfterScenario @temporary_storage_on_server + * + * @return void + * @throws Exception + */ + public function removeTemporaryStorageOnServerAfter(): void { + SetupHelper::rmDirOnServer( + TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER, + $this->getStepLineRef() + ); + } + + /** + * @AfterScenario + * + * @return void + */ + public function removeCreatedFilesAfter(): void { + foreach ($this->createdFiles as $file) { + \unlink($file); + } + } + + /** + * + * @param string $serverUrl + * + * @return void + */ + public function clearFileLocksForServer(string $serverUrl): void { + $response = OcsApiHelper::sendRequest( + $serverUrl, + $this->getAdminUsername(), + $this->getAdminPassword(), + 'delete', + "/apps/testing/api/v1/lockprovisioning", + $this->getStepLineRef(), + ["global" => "true"] + ); + Assert::assertEquals("200", $response->getStatusCode()); + } + + /** + * After Scenario. clear file locks + * + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function clearFileLocks(): void { + if (!OcisHelper::isTestingOnOcisOrReva()) { + $this->authContext->deleteTokenAuthEnforcedAfterScenario(); + $this->clearFileLocksForServer($this->getBaseUrl()); + if ($this->remoteBaseUrl !== $this->localBaseUrl) { + $this->clearFileLocksForServer($this->getRemoteBaseUrl()); + } + } + } + + /** + * @AfterScenario + * + * clear space id reference + * + * @return void + * @throws Exception + */ + public function clearSpaceId(): void { + if (\count(WebDavHelper::$spacesIdRef) > 0) { + WebDavHelper::$spacesIdRef = []; + } + } + + /** + * @BeforeSuite + * + * @param BeforeSuiteScope $scope + * + * @return void + * @throws Exception + */ + public static function useBigFileIDs(BeforeSuiteScope $scope): void { + if (OcisHelper::isTestingOnOcisOrReva()) { + return; + } + $fullUrl = \getenv('TEST_SERVER_URL'); + if (\substr($fullUrl, -1) !== '/') { + $fullUrl .= '/'; + } + $fullUrl .= "ocs/v1.php/apps/testing/api/v1/increasefileid"; + $suiteSettingsContexts = $scope->getSuite()->getSettings()['contexts']; + $adminUsername = null; + $adminPassword = null; + foreach ($suiteSettingsContexts as $context) { + if (isset($context[__CLASS__])) { + $adminUsername = $context[__CLASS__]['adminUsername']; + $adminPassword = $context[__CLASS__]['adminPassword']; + break; + } + } + + // get the admin username from the environment (if defined) + $adminUsernameFromEnvironment = self::getAdminUsernameFromEnvironment(); + if ($adminUsernameFromEnvironment !== false) { + $adminUsername = $adminUsernameFromEnvironment; + } + + // get the admin password from the environment (if defined) + $adminPasswordFromEnvironment = self::getAdminPasswordFromEnvironment(); + if ($adminPasswordFromEnvironment !== false) { + $adminPassword = $adminPasswordFromEnvironment; + } + + if (($adminUsername === null) || ($adminPassword === null)) { + throw new Exception( + "Could not find adminUsername and/or adminPassword in useBigFileIDs" + ); + } + + HttpRequestHelper::post( + $fullUrl, + '', + $adminUsername, + $adminPassword + ); + } + + /** + * runs a function on every server (LOCAL & REMOTE). + * The callable function receives the server (LOCAL or REMOTE) as first argument + * + * @param callable $callback + * + * @return array + */ + public function runFunctionOnEveryServer(callable $callback): array { + $previousServer = $this->getCurrentServer(); + $result = []; + foreach (['LOCAL', 'REMOTE'] as $server) { + $this->usingServer($server); + if (($server === 'LOCAL') + || $this->federatedServerExists() + ) { + $result[$server] = \call_user_func($callback, $server); + } + } + $this->usingServer($previousServer); + return $result; + } + + /** + * Verify that the tableNode contains expected headers + * + * @param TableNode $table + * @param array|null $requiredHeader + * @param array|null $allowedHeader + * + * @return void + * @throws Exception + */ + public function verifyTableNodeColumns(TableNode $table, ?array $requiredHeader = [], ?array $allowedHeader = []): void { + if (\count($table->getHash()) < 1) { + throw new Exception("Table should have at least one row."); + } + $tableHeaders = $table->getRows()[0]; + $allowedHeader = \array_unique(\array_merge($requiredHeader, $allowedHeader)); + if ($requiredHeader != []) { + foreach ($requiredHeader as $element) { + if (!\in_array($element, $tableHeaders)) { + throw new Exception("Row with header '$element' expected to be in table but not found"); + } + } + } + + if ($allowedHeader != []) { + foreach ($tableHeaders as $element) { + if (!\in_array($element, $allowedHeader)) { + throw new Exception("Row with header '$element' is not allowed in table but found"); + } + } + } + } + + /** + * Verify that the tableNode contains expected rows + * + * @param TableNode $table + * @param array $requiredRows + * @param array $allowedRows + * + * @return void + * @throws Exception + */ + public function verifyTableNodeRows(TableNode $table, array $requiredRows = [], array $allowedRows = []): void { + if (\count($table->getRows()) < 1) { + throw new Exception("Table should have at least one row."); + } + $tableHeaders = $table->getColumn(0); + $allowedRows = \array_unique(\array_merge($requiredRows, $allowedRows)); + if ($requiredRows != []) { + foreach ($requiredRows as $element) { + if (!\in_array($element, $tableHeaders)) { + throw new Exception("Row with name '$element' expected to be in table but not found"); + } + } + } + + if ($allowedRows != []) { + foreach ($tableHeaders as $element) { + if (!\in_array($element, $allowedRows)) { + throw new Exception("Row with name '$element' is not allowed in table but found"); + } + } + } + } + + /** + * Verify that the tableNode contains expected number of columns + * + * @param TableNode $table + * @param int $count + * + * @return void + * @throws Exception + */ + public function verifyTableNodeColumnsCount(TableNode $table, int $count): void { + if (\count($table->getRows()) < 1) { + throw new Exception("Table should have at least one row."); + } + $rowCount = \count($table->getRows()[0]); + if ($count !== $rowCount) { + throw new Exception("Table expected to have $count rows but found $rowCount"); + } + } + + /** + * @return void + */ + public function resetAppConfigs(): void { + // Set the required starting values for testing + $this->setCapabilities($this->getCommonSharingConfigs()); + } + + /** + * @Given the administrator has set the last login date for user :user to :days days ago + * @When the administrator sets the last login date for user :user to :days days ago using the testing API + * + * @param string $user + * @param string $days + * + * @return void + */ + public function theAdministratorSetsTheLastLoginDateForUserToDaysAgoUsingTheTestingApi(string $user, string $days): void { + $user = $this->getActualUsername($user); + $adminUser = $this->getAdminUsername(); + $baseUrl = "/apps/testing/api/v1/lastlogindate/$user"; + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $adminUser, + $this->getAdminPassword(), + 'POST', + $baseUrl, + $this->getStepLineRef(), + ['days' => $days], + $this->getOcsApiVersion() + ); + $this->setResponse($response); + } + + /** + * @param array $capabilitiesArray with each array entry containing keys for: + * ['capabilitiesApp'] the "app" name in the capabilities response + * ['capabilitiesParameter'] the parameter name in the capabilities response + * ['testingApp'] the "app" name as understood by "testing" + * ['testingParameter'] the parameter name as understood by "testing" + * ['testingState'] boolean state the parameter must be set to for the test + * + * @return void + */ + public function setCapabilities(array $capabilitiesArray): void { + AppConfigHelper::setCapabilities( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $capabilitiesArray, + $this->getStepLineRef() + ); + } + + /** + * After Scenario. restore trusted servers + * + * @AfterScenario @federation-app-required + * + * @return void + */ + public function restoreTrustedServersAfterScenario(): void { + $this->restoreTrustedServers('LOCAL'); + if ($this->federatedServerExists()) { + $this->restoreTrustedServers('REMOTE'); + } + } + + /** + * Invokes an OCC command + * + * @param array|null $args of the occ command + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return int exit code + * @throws Exception if ocPath has not been set yet or the testing app is not enabled + */ + public function runOcc( + ?array $args = [], + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ): int { + return $this->runOccWithEnvVariables( + $args, + null, + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath + ); + } + + /** + * Invokes an OCC command with an optional array of environment variables + * + * @param array|null $args of the occ command + * @param array|null $envVariables to be defined before the command is run + * @param string|null $adminUsername + * @param string|null $adminPassword + * @param string|null $baseUrl + * @param string|null $ocPath + * + * @return int exit code + * @throws Exception if ocPath has not been set yet or the testing app is not enabled + */ + public function runOccWithEnvVariables( + ?array $args = [], + ?array $envVariables = null, + ?string $adminUsername = null, + ?string $adminPassword = null, + ?string $baseUrl = null, + ?string $ocPath = null + ): int { + $args[] = '--no-ansi'; + if ($baseUrl == null) { + $baseUrl = $this->getBaseUrl(); + } + $return = SetupHelper::runOcc( + $args, + $this->getStepLineRef(), + $adminUsername, + $adminPassword, + $baseUrl, + $ocPath, + $envVariables + ); + $this->lastStdOut = $return['stdOut']; + $this->lastStdErr = $return['stdErr']; + $occStatusCode = (int)$return['code']; + return $occStatusCode; + } + + /** + * Find exception texts in stderr + * + * @return array of exception texts + */ + public function findExceptions(): array { + $exceptions = []; + $captureNext = false; + // the exception text usually appears after an "[Exception]" row + foreach (\explode("\n", $this->lastStdErr) as $line) { + if (\preg_match('/\[Exception\]/', $line)) { + $captureNext = true; + continue; + } + if ($captureNext) { + $exceptions[] = \trim($line); + $captureNext = false; + } + } + + return $exceptions; + } + + /** + * remember the result of the last occ command + * + * @param string[] $result associated array with "code", "stdOut", "stdErr" + * + * @return void + */ + public function setResultOfOccCommand(array $result): void { + Assert::assertIsArray($result); + Assert::assertArrayHasKey('code', $result); + Assert::assertArrayHasKey('stdOut', $result); + Assert::assertArrayHasKey('stdErr', $result); + $this->occLastCode = (int)$result['code']; + $this->lastStdOut = $result['stdOut']; + $this->lastStdErr = $result['stdErr']; + } + + /** + * @param string $sourceUser + * @param string $targetUser + * + * @return string|null + * @throws Exception + */ + public function findLastTransferFolderForUser(string $sourceUser, string $targetUser): ?string { + $foundPaths = []; + $responseXmlObject = $this->listFolderAndReturnResponseXml( + $targetUser, + '', + '1' + ); + $transferredElements = $responseXmlObject->xpath( + "//d:response/d:href[contains(., '/transferred%20from%20$sourceUser%20on%')]" + ); + foreach ($transferredElements as $transferredElement) { + // $transferredElement is an XML object. We want to work with the string in the XML element. + $path = \rawurldecode((string)$transferredElement); + $parts = \explode(' ', $path); + // store timestamp as key + $foundPaths[] = [ + 'date' => \strtotime(\trim($parts[4], '/')), + 'path' => $path, + ]; + } + if (empty($foundPaths)) { + return null; + } + + \usort( + $foundPaths, + function ($a, $b) { + return $a['date'] - $b['date']; + } + ); + + $davPath = \rtrim($this->getFullDavFilesPath($targetUser), '/'); + + $foundPath = \end($foundPaths)['path']; + // strip DAV path + return \substr($foundPath, \strlen($davPath) + 1); + } + + /** + * After Scenario. restore trusted servers + * + * @param string $server 'LOCAL'/'REMOTE' + * + * @return void + * @throws Exception + */ + public function restoreTrustedServers(string $server): void { + $currentTrustedServers = $this->getTrustedServers($server); + foreach (\array_diff($currentTrustedServers, $this->initialTrustedServer[$server]) as $url => $id) { + $this->appConfigurationContext->theAdministratorDeletesUrlFromTrustedServersUsingTheTestingApi($url); + } + foreach (\array_diff($this->initialTrustedServer[$server], $currentTrustedServers) as $url => $id) { + $this->appConfigurationContext->theAdministratorAddsUrlAsTrustedServerUsingTheTestingApi($url); + } + } + + /** + * + * @return void + * @throws Exception + */ + public function restoreParametersAfterScenario(): void { + if (!OcisHelper::isTestingOnOcisOrReva()) { + $this->authContext->deleteTokenAuthEnforcedAfterScenario(); + $user = $this->getCurrentUser(); + $this->setCurrentUser($this->getAdminUsername()); + $this->runFunctionOnEveryServer( + function ($server) { + $this->restoreParameters($server); + } + ); + $this->setCurrentUser($user); + } + } + + /** + * Get the array of trusted servers in format ["url" => "id"] + * + * @param string $server 'LOCAL'/'REMOTE' + * + * @return array + * @throws Exception + */ + public function getTrustedServers(string $server = 'LOCAL'): array { + if ($server === 'LOCAL') { + $url = $this->getLocalBaseUrl(); + } elseif ($server === 'REMOTE') { + $url = $this->getRemoteBaseUrl(); + } else { + throw new Exception(__METHOD__ . " Invalid value for server : $server"); + } + $adminUser = $this->getAdminUsername(); + $response = OcsApiHelper::sendRequest( + $url, + $adminUser, + $this->getAdminPassword(), + 'GET', + "/apps/testing/api/v1/trustedservers", + $this->getStepLineRef() + ); + if ($response->getStatusCode() !== 200) { + throw new Exception("Could not get the list of trusted servers" . $response->getBody()->getContents()); + } + $responseXml = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $serverData = \json_decode( + \json_encode( + $responseXml->data + ), + true + ); + if (!\array_key_exists('element', $serverData)) { + return []; + } else { + return isset($serverData['element'][0]) ? + \array_column($serverData['element'], 'id', 'url') : + \array_column($serverData, 'id', 'url'); + } + } + + /** + * @param string $method http request method + * @param string $property property in form d:getetag + * if property is `doesnotmatter` body is also set `doesnotmatter` + * + * @return string + */ + public function getBodyForOCSRequest(string $method, string $property): ?string { + $body = null; + if ($method === 'PROPFIND') { + $body = '<' . $property . '/>'; + } elseif ($method === 'LOCK') { + $body = " <" . $property . " />"; + } elseif ($method === 'PROPPATCH') { + if ($property === 'favorite') { + $property = '1'; + } + $body = '' . $property . ''; + } + if ($property === '') { + $body = ''; + } + return $body; + } + + /** + * @BeforeScenario + * + * @return void + * @throws Exception + */ + public function prepareParametersBeforeScenario(): void { + if (!OcisHelper::isTestingOnOcisOrReva()) { + $user = $this->getCurrentUser(); + $this->setCurrentUser($this->getAdminUsername()); + $previousServer = $this->getCurrentServer(); + foreach (['LOCAL', 'REMOTE'] as $server) { + if (($server === 'LOCAL') || $this->federatedServerExists()) { + $this->usingServer($server); + $this->resetAppConfigs(); + $result = SetupHelper::runOcc( + ['config:list', '--private'], + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getBaseUrl(), + $this->getOcPath() + ); + $this->savedConfigList[$server] = \json_decode($result['stdOut'], true); + } + } + $this->usingServer($previousServer); + $this->setCurrentUser($user); + } + } + + /** + * Before Scenario to Save trusted Servers + * + * @BeforeScenario @federation-app-required + * + * @return void + * @throws Exception + */ + public function setInitialTrustedServersBeforeScenario(): void { + $this->initialTrustedServer = [ + 'LOCAL' => $this->getTrustedServers(), + 'REMOTE' => $this->getTrustedServers('REMOTE') + ]; + } + + /** + * restore settings of the system and delete new settings that were created in the test runs + * + * @param string $server LOCAL|REMOTE + * + * @return void + * + * @throws Exception + * @throws GuzzleException + * + */ + private function restoreParameters(string $server): void { + $commands = []; + if ($this->isTestingWithLdap()) { + $this->resetOldLdapConfig(); + } + $result = SetupHelper::runOcc( + ['config:list'], + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getBaseUrl(), + $this->getOcPath() + ); + $currentConfigList = \json_decode($result['stdOut'], true); + foreach ($currentConfigList['system'] as $configKey => $configValue) { + if (!\array_key_exists( + $configKey, + $this->savedConfigList[$server]['system'] + ) + ) { + $commands[] = ["command" => ['config:system:delete', $configKey]]; + } + } + foreach ($this->savedConfigList[$server]['system'] as $configKey => $configValue) { + if (!\array_key_exists($configKey, $currentConfigList["system"]) + || $currentConfigList["system"][$configKey] !== $this->savedConfigList[$server]['system'][$configKey] + ) { + $commands[] = ["command" => ['config:system:set', "--type=json", "--value=" . \json_encode($configValue), $configKey]]; + } + } + foreach ($currentConfigList['apps'] as $appName => $appSettings) { + foreach ($appSettings as $configKey => $configValue) { + //only check if the app was there in the original configuration + if (\array_key_exists($appName, $this->savedConfigList[$server]['apps']) + && !\array_key_exists( + $configKey, + $this->savedConfigList[$server]['apps'][$appName] + ) + ) { + $commands[] = ["command" => ['config:app:delete', $appName, $configKey]]; + } elseif (\array_key_exists($appName, $this->savedConfigList[$server]['apps']) + && \array_key_exists($configKey, $this->savedConfigList[$server]['apps'][$appName]) + && $this->savedConfigList[$server]['apps'][$appName][$configKey] !== $configValue + ) { + // Do not accidentally disable apps here (perhaps too early) + // That is done in Provisioning.php restoreAppEnabledDisabledState() + if ($configKey !== "enabled") { + $commands[] = [ + "command" => [ + 'config:app:set', + $appName, + $configKey, + "--value=" . $this->savedConfigList[$server]['apps'][$appName][$configKey] + ] + ]; + } + } + } + } + SetupHelper::runBulkOcc( + $commands, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getBaseUrl() + ); + } +} diff --git a/tests/acceptance/features/bootstrap/FilesVersionsContext.php b/tests/acceptance/features/bootstrap/FilesVersionsContext.php new file mode 100644 index 00000000000..7f26e3ed730 --- /dev/null +++ b/tests/acceptance/features/bootstrap/FilesVersionsContext.php @@ -0,0 +1,443 @@ + + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use TestHelpers\HttpRequestHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * Steps that relate to files_versions app + */ +class FilesVersionsContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @param string $fileId + * + * @return string + */ + private function getVersionsPathForFileId(string $fileId):string { + return "/meta/$fileId/v"; + } + + /** + * @When user :user tries to get versions of file :file from :fileOwner + * + * @param string $user + * @param string $file + * @param string $fileOwner + * + * @return void + * @throws Exception + */ + public function userTriesToGetFileVersions(string $user, string $file, string $fileOwner):void { + $user = $this->featureContext->getActualUsername($user); + $fileOwner = $this->featureContext->getActualUsername($fileOwner); + $fileId = $this->featureContext->getFileIdForPath($fileOwner, $file); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $fileOwner not found (the file may not exist)"); + $response = $this->featureContext->makeDavRequest( + $user, + "PROPFIND", + $this->getVersionsPathForFileId($fileId), + null, + null, + null, + '2' + ); + $this->featureContext->setResponse($response, $user); + } + + /** + * @When user :user gets the number of versions of file :file + * + * @param string $user + * @param string $file + * + * @return void + * @throws Exception + */ + public function userGetsFileVersions(string $user, string $file):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $file); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $user not found (the file may not exist)"); + $response = $this->featureContext->makeDavRequest( + $user, + "PROPFIND", + $this->getVersionsPathForFileId($fileId), + null, + null, + null, + '2' + ); + $this->featureContext->setResponse($response, $user); + } + + /** + * @When user :user gets the version metadata of file :file + * + * @param string $user + * @param string $file + * + * @return void + * @throws Exception + */ + public function userGetsVersionMetadataOfFile(string $user, string $file):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $file); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $user not found (the file may not exist)"); + $body = ' + + + + + + '; + $response = $this->featureContext->makeDavRequest( + $user, + "PROPFIND", + $this->getVersionsPathForFileId($fileId), + null, + $body, + null, + '2' + ); + $this->featureContext->setResponse($response, $user); + } + + /** + * @When user :user restores version index :versionIndex of file :path using the WebDAV API + * @Given user :user has restored version index :versionIndex of file :path + * + * @param string $user + * @param int $versionIndex + * @param string $path + * + * @return void + * @throws Exception + */ + public function userRestoresVersionIndexOfFile(string $user, int $versionIndex, string $path):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $path); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)"); + $responseXml = $this->listVersionFolder($user, $fileId, 1); + $xmlPart = $responseXml->xpath("//d:response/d:href"); + //restoring the version only works with DAV path v2 + $destinationUrl = $this->featureContext->getBaseUrl() . "/" . + WebDavHelper::getDavPath($user, 2) . \trim($path, "/"); + $fullUrl = $this->featureContext->getBaseUrlWithoutPath() . + $xmlPart[$versionIndex]; + $response = HttpRequestHelper::sendRequest( + $fullUrl, + $this->featureContext->getStepLineRef(), + 'COPY', + $user, + $this->featureContext->getPasswordForUser($user), + ['Destination' => $destinationUrl] + ); + $this->featureContext->setResponse($response, $user); + } + + /** + * @Then the version folder of file :path for user :user should contain :count element(s) + * + * @param string $path + * @param string $user + * @param int $count + * + * @return void + * @throws Exception + */ + public function theVersionFolderOfFileShouldContainElements( + string $path, + string $user, + int $count + ):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $path); + Assert::assertNotNull($fileId, __METHOD__ . " file $path user $user not found (the file may not exist)"); + $this->theVersionFolderOfFileIdShouldContainElements($fileId, $user, $count); + } + + /** + * @Then the version folder of fileId :fileId for user :user should contain :count element(s) + * + * @param string $fileId + * @param string $user + * @param int $count + * + * @return void + * @throws Exception + */ + public function theVersionFolderOfFileIdShouldContainElements( + string $fileId, + string $user, + int $count + ):void { + $responseXml = $this->listVersionFolder($user, $fileId, 1); + $xmlPart = $responseXml->xpath("//d:prop/d:getetag"); + Assert::assertEquals( + $count, + \count($xmlPart) - 1, + "could not find $count version element(s) in \n" . $responseXml->asXML() + ); + } + + /** + * @Then the content length of file :path with version index :index for user :user in versions folder should be :length + * + * @param string $path + * @param int $index + * @param string $user + * @param int $length + * + * @return void + * @throws Exception + */ + public function theContentLengthOfFileForUserInVersionsFolderIs( + string $path, + int $index, + string $user, + int $length + ):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $path); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)"); + $responseXml = $this->listVersionFolder( + $user, + $fileId, + 1, + ['getcontentlength'] + ); + $xmlPart = $responseXml->xpath("//d:prop/d:getcontentlength"); + Assert::assertEquals( + $length, + (int) $xmlPart[$index], + "The content length of file {$path} with version {$index} for user {$user} was + expected to be {$length} but the actual content length is {$xmlPart[$index]}" + ); + } + + /** + * @Then /^as (?:users|user) "([^"]*)" the authors of the versions of file "([^"]*)" should be:$/ + * + * @param string $users comma-separated list of usernames + * @param string $filename + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function asUsersAuthorsOfVersionsOfFileShouldBe( + string $users, + string $filename, + TableNode $table + ): void { + $this->featureContext->verifyTableNodeColumns( + $table, + ['index', 'author'] + ); + $requiredVersionMetadata = $table->getHash(); + $usersArray = \explode(",", $users); + foreach ($usersArray as $username) { + $actualUsername = $this->featureContext->getActualUsername($username); + $this->userGetsVersionMetadataOfFile($actualUsername, $filename); + foreach ($requiredVersionMetadata as $versionMetadata) { + $this->featureContext->theAuthorOfEditedVersionFile( + $versionMetadata['index'], + $versionMetadata['author'] + ); + } + } + } + + /** + * @When user :user downloads the version of file :path with the index :index + * + * @param string $user + * @param string $path + * @param string $index + * + * @return void + * @throws Exception + */ + public function downloadVersion(string $user, string $path, string $index):void { + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $path); + Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)"); + $index = (int)$index; + $responseXml = $this->listVersionFolder($user, $fileId, 1); + $xmlPart = $responseXml->xpath("//d:response/d:href"); + if (!isset($xmlPart[$index])) { + Assert::fail( + 'could not find version of path "' . $path . '" with index "' . $index . '"' + ); + } + // the href already contains the path + $url = WebDavHelper::sanitizeUrl( + $this->featureContext->getBaseUrlWithoutPath() . $xmlPart[$index] + ); + $response = HttpRequestHelper::get( + $url, + $this->featureContext->getStepLineRef(), + $user, + $this->featureContext->getPasswordForUser($user) + ); + $this->featureContext->setResponse($response, $user); + } + + /** + * @Then /^the content of version index "([^"]*)" of file "([^"]*)" for user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $index + * @param string $path + * @param string $user + * @param string $content + * + * @return void + * @throws Exception + */ + public function theContentOfVersionIndexOfFileForUserShouldBe( + string $index, + string $path, + string $user, + string $content + ): void { + $this->downloadVersion($user, $path, $index); + $this->featureContext->theHTTPStatusCodeShouldBe("200"); + $this->featureContext->downloadedContentShouldBe($content); + } + + /** + * @When /^user "([^"]*)" retrieves the meta information of (file|fileId) "([^"]*)" using the meta API$/ + * + * @param string $user + * @param string $fileOrFileId + * @param string $path + * + * @return void + */ + public function userGetMetaInfo(string $user, string $fileOrFileId, string $path):void { + $user = $this->featureContext->getActualUsername($user); + $baseUrl = $this->featureContext->getBaseUrl(); + $password = $this->featureContext->getPasswordForUser($user); + + if ($fileOrFileId === "file") { + $fileId = $this->featureContext->getFileIdForPath($user, $path); + $metaPath = "/meta/$fileId/"; + } else { + $metaPath = "/meta/$path/"; + } + + $body = ' + + + + + '; + + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPFIND", + $metaPath, + ['Content-Type' => 'text/xml','Depth' => '0'], + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion(), + null + ); + $this->featureContext->setResponse($response); + $responseXml = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $this->featureContext->setResponseXmlObject($responseXml); + } + + /** + * returns the result parsed into an SimpleXMLElement + * with an registered namespace with 'd' as prefix and 'DAV:' as namespace + * + * @param string $user + * @param string $fileId + * @param int $folderDepth + * @param string[]|null $properties + * + * @return SimpleXMLElement + * @throws Exception + */ + public function listVersionFolder( + string $user, + string $fileId, + int $folderDepth, + ?array $properties = null + ):SimpleXMLElement { + if (!$properties) { + $properties = [ + 'getetag' + ]; + } + $user = $this->featureContext->getActualUsername($user); + $password = $this->featureContext->getPasswordForUser($user); + $response = WebDavHelper::propfind( + $this->featureContext->getBaseUrl(), + $user, + $password, + $this->getVersionsPathForFileId($fileId), + $properties, + $this->featureContext->getStepLineRef(), + (string) $folderDepth, + "versions" + ); + return HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/LoggingContext.php b/tests/acceptance/features/bootstrap/LoggingContext.php new file mode 100644 index 00000000000..c361da5202a --- /dev/null +++ b/tests/acceptance/features/bootstrap/LoggingContext.php @@ -0,0 +1,617 @@ + + * @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use TestHelpers\LoggingHelper; +use TestHelpers\OcisHelper; +use TestHelpers\SetupHelper; + +require_once 'bootstrap.php'; + +/** + * Context to make the Logging steps available + */ +class LoggingContext implements Context { + /** + * @var FeatureContext + */ + private $featureContext; + + private $oldLogLevel = null; + private $oldLogBackend = null; + private $oldLogTimezone = null; + + /** + * checks for specific rows in the log file. + * order of the table has to be the same as in the log file + * empty cells in the table will not be checked! + * + * @Then /^the last lines of the log file should contain log-entries (with|containing|matching) these attributes:$/ + * + * @param string $comparingMode + * @param int $ignoredLines + * @param TableNode|null $expectedLogEntries table with headings that correspond + * to the json keys in the log entry + * e.g. + * |user|app|method|message| + * + * @return void + * @throws Exception + */ + public function theLastLinesOfTheLogFileShouldContainEntriesWithTheseAttributes( + string $comparingMode, + int $ignoredLines = 0, + ?TableNode $expectedLogEntries = null + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + // So skip processing this test step. + return; + } + $ignoredLines = (int) $ignoredLines; + //-1 because getRows gives also the header + $linesToRead = \count($expectedLogEntries->getRows()) - 1 + $ignoredLines; + $logLines = LoggingHelper::getLogFileContent( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getStepLineRef(), + $linesToRead + ); + $lineNo = 0; + foreach ($expectedLogEntries as $expectedLogEntry) { + $logEntry = \json_decode($logLines[$lineNo], true); + if ($logEntry === null) { + throw new Exception("the log line :\n{$logLines[$lineNo]} is not valid JSON"); + } + + foreach (\array_keys($expectedLogEntry) as $attribute) { + if ($comparingMode === 'matching') { + $expectedLogEntry[$attribute] + = $this->featureContext->substituteInLineCodes( + $expectedLogEntry[$attribute], + null, + ['preg_quote' => ['/']] + ); + } else { + $expectedLogEntry[$attribute] + = $this->featureContext->substituteInLineCodes( + $expectedLogEntry[$attribute] + ); + } + + if ($expectedLogEntry[$attribute] !== "") { + Assert::assertArrayHasKey( + $attribute, + $logEntry, + "could not find attribute: '$attribute' in log entry: '{$logLines[$lineNo]}'" + ); + $message = "log entry:\n{$logLines[$lineNo]}\n"; + if (!\is_string($logEntry[$attribute])) { + $logEntry[$attribute] = \json_encode( + $logEntry[$attribute], + JSON_UNESCAPED_SLASHES + ); + } + if ($comparingMode === 'with') { + Assert::assertEquals( + $expectedLogEntry[$attribute], + $logEntry[$attribute], + $message + ); + } elseif ($comparingMode === 'containing') { + Assert::assertStringContainsString( + $expectedLogEntry[$attribute], + $logEntry[$attribute], + $message + ); + } elseif ($comparingMode === 'matching') { + Assert::assertMatchesRegularExpression( + $expectedLogEntry[$attribute], + $logEntry[$attribute], + $message + ); + } else { + throw new \InvalidArgumentException( + "$comparingMode is not a valid mode" + ); + } + } + } + $lineNo++; + if (($lineNo + $ignoredLines) >= $linesToRead) { + break; + } + } + } + + /** + * alternative wording theLastLinesOfTheLogFileShouldContainEntriesWithTheseAttributes() + * + * @Then /^the last lines of the log file, ignoring the last (\d+) lines, should contain log-entries (with|containing|matching) these attributes:$/ + * + * @param int $ignoredLines + * @param string $comparingMode + * @param TableNode $expectedLogEntries + * + * @return void + * @throws Exception + */ + public function theLastLinesOfTheLogFileIgnoringSomeShouldContainEntries( + int $ignoredLines, + string $comparingMode, + TableNode $expectedLogEntries + ):void { + $this->theLastLinesOfTheLogFileShouldContainEntriesWithTheseAttributes( + $comparingMode, + $ignoredLines, + $expectedLogEntries + ); + } + + /** + * alternative wording theLastLinesOfTheLogFileShouldContainEntriesWithTheseAttributes() + * + * @Then /^the last lines of the log file, ignoring the last line, should contain log-entries (with|containing|matching) these attributes:$/ + * + * @param string $comparingMode + * @param TableNode $expectedLogEntries + * + * @return void + * @throws Exception + */ + public function theLastLinesOfTheLogFileIgnoringLastShouldContainEntries( + string $comparingMode, + TableNode $expectedLogEntries + ):void { + $this->theLastLinesOfTheLogFileShouldContainEntriesWithTheseAttributes( + $comparingMode, + 1, + $expectedLogEntries + ); + } + + /** + * wrapper around assertLogFileContainsAtLeastOneEntryMatchingTable() + * + * @Then the log file should contain at least one entry matching each of these lines: + * + * @param TableNode $expectedLogEntries table with headings that correspond + * to the json keys in the log entry + * e.g. + * |user|app|method|message| + * + * @return void + * @throws Exception + * @see assertLogFileContainsAtLeastOneEntryMatchingTable() + */ + public function logFileShouldContainEntriesMatching( + TableNode $expectedLogEntries + ):void { + $this->assertLogFileContainsAtLeastOneEntryMatchingTable( + true, + $expectedLogEntries + ); + } + + /** + * wrapper around assertLogFileContainsAtLeastOneEntryMatchingTable() + * + * @Then the log file should contain at least one entry matching the regular expressions in each of these lines: + * + * @param TableNode $expectedLogEntries + * + * @return void + * @throws Exception + * @see assertLogFileContainsAtLeastOneEntryMatchingTable() + */ + public function logFileShouldContainEntriesMatchingRegularExpression( + TableNode $expectedLogEntries + ):void { + $this->assertLogFileContainsAtLeastOneEntryMatchingTable( + true, + $expectedLogEntries, + true + ); + } + + /** + * @Then the log file should not contain any entry matching the regular expressions in each of these lines: + * + * @param TableNode $expectedLogEntries + * + * @return void + * @throws Exception + */ + public function logFileShouldNotContainAnyTheEntriesMatchingTheRegularExpression( + TableNode $expectedLogEntries + ):void { + $this->assertLogFileContainsAtLeastOneEntryMatchingTable( + false, + $expectedLogEntries, + true + ); + } + + /** + * checks that every line in the table has at least one + * corresponding line in the log file + * empty cells in the table will not be checked! + * + * @param boolean $shouldOrNot if true the table entries are expected to match + * at least one entry in the log + * if false the table entries are expected not + * to match any log in the log file + * @param TableNode $expectedLogEntries table with headings that correspond + * to the json keys in the log entry + * e.g. + * |user|app|method|message| + * @param boolean $regexCompare if true the table entries are expected + * to be regular expressions + * + * @return void + * @throws Exception + */ + private function assertLogFileContainsAtLeastOneEntryMatchingTable( + bool $shouldOrNot, + TableNode $expectedLogEntries, + bool $regexCompare = false + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + // So skip processing this test step. + return; + } + $logLines = LoggingHelper::getLogFileContent( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getStepLineRef() + ); + $expectedLogEntries = $expectedLogEntries->getHash(); + foreach ($logLines as $logLine) { + $logEntry = \json_decode($logLine, true); + if ($logEntry === null) { + throw new Exception("the log line :\n{$logLine} is not valid JSON"); + } + //reindex the array, we might have deleted entries + $expectedLogEntries = \array_values($expectedLogEntries); + for ($entryNo = 0; $entryNo < \count($expectedLogEntries); $entryNo++) { + $count = 0; + $expectedLogEntry = $expectedLogEntries[$entryNo]; + $foundLine = true; + foreach (\array_keys($expectedLogEntry) as $attribute) { + if ($expectedLogEntry[$attribute] === "") { + //don't check empty table entries + continue; + } + if (!\array_key_exists($attribute, $logEntry)) { + //this line does not have the attribute we are looking for + $foundLine = false; + break; + } + if (!\is_string($logEntry[$attribute])) { + $logEntry[$attribute] = \json_encode( + $logEntry[$attribute], + JSON_UNESCAPED_SLASHES + ); + } + + if ($regexCompare === true) { + $expectedLogEntry[$attribute] + = $this->featureContext->substituteInLineCodes( + $expectedLogEntry[$attribute], + null, + ['preg_quote' => ['/']] + ); + $matchAttribute = \preg_match( + $expectedLogEntry[$attribute], + $logEntry[$attribute] + ); + } else { + $expectedLogEntry[$attribute] + = $this->featureContext->substituteInLineCodes( + $expectedLogEntry[$attribute] + ); + $matchAttribute + = ($expectedLogEntry[$attribute] === $logEntry[$attribute]); + } + if (!$matchAttribute) { + $foundLine = false; + break; + } + if ($matchAttribute and !$shouldOrNot) { + $count += 1; + Assert::assertNotEquals( + $count, + \count($expectedLogEntry), + "The entry matches" + ); + } + } + if ($foundLine === true) { + unset($expectedLogEntries[$entryNo]); + } + } + } + $notFoundLines = \print_r($expectedLogEntries, true); + if ($shouldOrNot) { + Assert::assertEmpty( + $expectedLogEntries, + "could not find these expected line(s):\n $notFoundLines" + ); + } + } + + /** + * fails if there is at least one line in the log file that matches all + * given attributes + * attributes in the table that are empty will match any value in the + * corresponding attribute in the log file + * + * @Then /^the log file should not contain any log-entries (with|containing) these attributes:$/ + * + * @param string $withOrContaining + * @param TableNode $logEntriesExpectedNotToExist table with headings that + * correspond to the json + * keys in the log entry + * e.g. + * |user|app|method|message| + * + * @return void + * @throws Exception + */ + public function theLogFileShouldNotContainAnyLogEntriesWithTheseAttributes( + $withOrContaining, + TableNode $logEntriesExpectedNotToExist + ):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // Currently we don't interact with the log file on reva or OCIS + // So skip processing this test step. + return; + } + $logLines = LoggingHelper::getLogFileContent( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getStepLineRef() + ); + foreach ($logLines as $logLine) { + $logEntry = \json_decode($logLine, true); + if ($logEntry === null) { + throw new Exception("the log line :\n$logLine is not valid JSON"); + } + foreach ($logEntriesExpectedNotToExist as $logEntryExpectedNotToExist) { + $match = true; // start by assuming the worst, we match the unwanted log entry + foreach (\array_keys($logEntryExpectedNotToExist) as $attribute) { + $logEntryExpectedNotToExist[$attribute] + = $this->featureContext->substituteInLineCodes( + $logEntryExpectedNotToExist[$attribute] + ); + + if (isset($logEntry[$attribute]) && ($logEntryExpectedNotToExist[$attribute] !== "")) { + if ($withOrContaining === 'with') { + $match = ($logEntryExpectedNotToExist[$attribute] === $logEntry[$attribute]); + } else { + $match = (\strpos($logEntry[$attribute], $logEntryExpectedNotToExist[$attribute]) !== false); + } + } + if (!isset($logEntry[$attribute])) { + $match = false; + } + if (!$match) { + break; + } + } + } + Assert::assertFalse( + $match, + "found a log entry that should not be there\n$logLine\n" + ); + } + } + + /** + * @When the owncloud log level is set to :logLevel + * + * @param string $logLevel (debug|info|warning|error|fatal) + * + * @return void + * @throws Exception + */ + public function owncloudLogLevelIsSetTo(string $logLevel):void { + LoggingHelper::setLogLevel( + $logLevel, + $this->featureContext->getStepLineRef() + ); + } + + /** + * @Given the owncloud log level has been set to :logLevel + * + * @param string $logLevel (debug|info|warning|error|fatal) + * + * @return void + * @throws Exception + */ + public function owncloudLogLevelHasBeenSetTo(string $logLevel):void { + $this->owncloudLogLevelIsSetTo($logLevel); + $logLevelArray = LoggingHelper::LOG_LEVEL_ARRAY; + $logLevelExpected = \array_search($logLevel, $logLevelArray); + $logLevelActual = \array_search( + LoggingHelper::getLogLevel( + $this->featureContext->getStepLineRef() + ), + $logLevelArray + ); + Assert::assertEquals( + $logLevelExpected, + $logLevelActual, + "The expected log level is {$logLevelExpected} but the log level has been set to {$logLevelActual}" + ); + } + + /** + * @When the owncloud log backend is set to :backend + * + * @param string $backend (owncloud|syslog|errorlog) + * + * @return void + * @throws Exception + */ + public function owncloudLogBackendIsSetTo(string $backend):void { + LoggingHelper::setLogBackend( + $backend, + $this->featureContext->getStepLineRef() + ); + } + + /** + * @Given the owncloud log backend has been set to :backend + * + * @param string $expectedBackend (owncloud|syslog|errorlog) + * + * @return void + * @throws Exception + */ + public function owncloudLogBackendHasBeenSetTo(string $expectedBackend):void { + $this->owncloudLogBackendIsSetTo($expectedBackend); + $currentBackend = LoggingHelper::getLogBackend( + $this->featureContext->getStepLineRef() + ); + Assert::assertEquals( + $expectedBackend, + $currentBackend, + "The owncloud log backend was expected to be set to {$expectedBackend} but got {$currentBackend}" + ); + } + + /** + * @When the owncloud log timezone is set to :timezone + * + * @param string $timezone + * + * @return void + * @throws Exception + */ + public function owncloudLogTimezoneIsSetTo(string $timezone):void { + LoggingHelper::setLogTimezone( + $timezone, + $this->featureContext->getStepLineRef() + ); + } + + /** + * @Given the owncloud log timezone has been set to :timezone + * + * @param string $expectedTimezone + * + * @return void + * @throws Exception + */ + public function owncloudLogTimezoneHasBeenSetTo(string $expectedTimezone):void { + $this->owncloudLogTimezoneIsSetTo($expectedTimezone); + $currentTimezone = LoggingHelper::getLogTimezone( + $this->featureContext->getStepLineRef() + ); + Assert::assertEquals( + $expectedTimezone, + $currentTimezone, + "The owncloud log timezone was expected to be set to {$expectedTimezone}, but got {$currentTimezone}" + ); + } + + /** + * @When the owncloud log is cleared + * @Given the owncloud log has been cleared + * + * checks for the httpRequest is done inside clearLogFile function + * + * @return void + * @throws Exception + */ + public function theOwncloudLogIsCleared():void { + LoggingHelper::clearLogFile( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getStepLineRef() + ); + } + + /** + * After Scenario for logging. Sets back old log settings + * + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function tearDownScenarioLogging():void { + LoggingHelper::restoreLoggingStatus( + $this->oldLogLevel, + $this->oldLogBackend, + $this->oldLogTimezone, + $this->featureContext->getStepLineRef() + ); + } + + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function setUpScenario(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + SetupHelper::init( + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + } + + /** + * Before Scenario for logging. Saves current log settings + * + * @BeforeScenario + * + * @return void + * @throws Exception + */ + public function setUpScenarioLogging():void { + $logging = LoggingHelper::getLogInfo( + $this->featureContext->getStepLineRef() + ); + $this->oldLogLevel = $logging["level"]; + $this->oldLogBackend = $logging["backend"]; + $this->oldLogTimezone = $logging["timezone"]; + } +} diff --git a/tests/acceptance/features/bootstrap/OCSContext.php b/tests/acceptance/features/bootstrap/OCSContext.php new file mode 100644 index 00000000000..75f7ea6a8e4 --- /dev/null +++ b/tests/acceptance/features/bootstrap/OCSContext.php @@ -0,0 +1,1011 @@ + + * @copyright Copyright (c) 2019, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; +use TestHelpers\OcsApiHelper; +use TestHelpers\TranslationHelper; + +require_once 'bootstrap.php'; + +/** + * steps needed to send requests to the OCS API + */ +class OCSContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @When /^the user sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)"$/ + * + * @param string $verb + * @param string $url + * + * @return void + */ + public function theUserSendsToOcsApiEndpoint(string $verb, string $url):void { + $this->theUserSendsToOcsApiEndpointWithBody($verb, $url, null); + } + + /** + * @Given /^the user has sent HTTP method "([^"]*)" to OCS API endpoint "([^"]*)"$/ + * + * @param string $verb + * @param string $url + * + * @return void + */ + public function theUserHasSentToOcsApiEndpoint(string $verb, string $url):void { + $this->theUserSendsToOcsApiEndpointWithBody($verb, $url, null); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)"$/ + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" using password "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string|null $password + * + * @return void + */ + public function userSendsToOcsApiEndpoint(string $user, string $verb, string $url, ?string $password = null):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $verb, + $url, + null, + $password + ); + } + + /** + * @Given /^user "([^"]*)" has sent HTTP method "([^"]*)" to API endpoint "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string|null $password + * + * @return void + */ + public function userHasSentToOcsApiEndpoint(string $user, string $verb, string $url, ?string $password = null):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $verb, + $url, + null, + $password + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $verb + * @param string $url + * @param TableNode|null $body + * @param string|null $password + * @param array|null $headers + * + * @return void + */ + public function userSendsHTTPMethodToOcsApiEndpointWithBody( + string $user, + string $verb, + string $url, + ?TableNode $body = null, + ?string $password = null, + ?array $headers = null + ):void { + /** + * array of the data to be sent in the body. + * contains $body data converted to an array + * + * @var array $bodyArray + */ + $bodyArray = []; + if ($body instanceof TableNode) { + $bodyArray = $body->getRowsHash(); + } elseif ($body !== null && \is_array($body)) { + $bodyArray = $body; + } + + if ($user !== 'UNAUTHORIZED_USER') { + if ($password === null) { + $password = $this->featureContext->getPasswordForUser($user); + } + $user = $this->featureContext->getActualUsername($user); + } else { + $user = null; + $password = null; + } + $response = OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $user, + $password, + $verb, + $url, + $this->featureContext->getStepLineRef(), + $bodyArray, + $this->featureContext->getOcsApiVersion(), + $headers + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $verb + * @param string $url + * @param TableNode|null $body + * + * @return void + */ + public function adminSendsHttpMethodToOcsApiEndpointWithBody( + string $verb, + string $url, + ?TableNode $body + ):void { + $admin = $this->featureContext->getAdminUsername(); + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $admin, + $verb, + $url, + $body + ); + } + + /** + * @param string $verb + * @param string $url + * @param TableNode|null $body + * + * @return void + */ + public function theUserSendsToOcsApiEndpointWithBody(string $verb, string $url, ?TableNode $body = null):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $this->featureContext->getCurrentUser(), + $verb, + $url, + $body + ); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with body$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param TableNode|null $body + * @param string|null $password + * + * @return void + */ + public function userSendHTTPMethodToOcsApiEndpointWithBody( + string $user, + string $verb, + string $url, + ?TableNode $body = null, + ?string $password = null + ):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $verb, + $url, + $body, + $password + ); + } + + /** + * @Given /^user "([^"]*)" has sent HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with body$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param TableNode|null $body + * @param string $password + * + * @return void + */ + public function userHasSentHTTPMethodToOcsApiEndpointWithBody( + string $user, + string $verb, + string $url, + ?TableNode $body = null, + ?string $password = null + ):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $verb, + $url, + $body, + $password + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the administrator sends HTTP method :verb to OCS API endpoint :url + * @When the administrator sends HTTP method :verb to OCS API endpoint :url using password :password + * + * @param string $verb + * @param string $url + * @param string|null $password + * + * @return void + */ + public function theAdministratorSendsHttpMethodToOcsApiEndpoint( + string $verb, + string $url, + ?string $password = null + ):void { + $admin = $this->featureContext->getAdminUsername(); + $this->userSendsToOcsApiEndpoint($admin, $verb, $url, $password); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with headers$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param TableNode $headersTable + * + * @return void + * @throws Exception + */ + public function userSendsToOcsApiEndpointWithHeaders( + string $user, + string $verb, + string $url, + TableNode $headersTable + ):void { + $user = $this->featureContext->getActualUsername($user); + $password = $this->featureContext->getPasswordForUser($user); + $this->userSendsToOcsApiEndpointWithHeadersAndPassword( + $user, + $verb, + $url, + $password, + $headersTable + ); + } + + /** + * @When /^the administrator sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with headers$/ + * + * @param string $verb + * @param string $url + * @param TableNode $headersTable + * + * @return void + * @throws Exception + */ + public function administratorSendsToOcsApiEndpointWithHeaders( + string $verb, + string $url, + TableNode $headersTable + ):void { + $this->userSendsToOcsApiEndpointWithHeaders( + $this->featureContext->getAdminUsername(), + $verb, + $url, + $headersTable + ); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with headers using password "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string $password + * @param TableNode $headersTable + * + * @return void + * @throws Exception + */ + public function userSendsToOcsApiEndpointWithHeadersAndPassword( + string $user, + string $verb, + string $url, + string $password, + TableNode $headersTable + ):void { + $this->featureContext->verifyTableNodeColumns( + $headersTable, + ['header', 'value'] + ); + $user = $this->featureContext->getActualUsername($user); + $headers = []; + foreach ($headersTable as $row) { + $headers[$row['header']] = $row ['value']; + } + + $response = OcsApiHelper::sendRequest( + $this->featureContext->getBaseUrl(), + $user, + $password, + $verb, + $url, + $this->featureContext->getStepLineRef(), + [], + $this->featureContext->getOcsApiVersion(), + $headers + ); + $this->featureContext->setResponse($response); + } + + /** + * @When /^the administrator sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with headers using password "([^"]*)"$/ + * + * @param string $verb + * @param string $url + * @param string $password + * @param TableNode $headersTable + * + * @return void + * @throws Exception + */ + public function administratorSendsToOcsApiEndpointWithHeadersAndPassword( + string $verb, + string $url, + string $password, + TableNode $headersTable + ):void { + $this->userSendsToOcsApiEndpointWithHeadersAndPassword( + $this->featureContext->getAdminUsername(), + $verb, + $url, + $password, + $headersTable + ); + } + + /** + * @When the administrator sends HTTP method :verb to OCS API endpoint :url with body + * + * @param string $verb + * @param string $url + * @param TableNode|null $body + * + * @return void + */ + public function theAdministratorSendsHttpMethodToOcsApiEndpointWithBody( + string $verb, + string $url, + ?TableNode $body + ):void { + $this->adminSendsHttpMethodToOcsApiEndpointWithBody( + $verb, + $url, + $body + ); + } + + /** + * @Given the administrator has sent HTTP method :verb to OCS API endpoint :url with body + * + * @param string $verb + * @param string $url + * @param TableNode|null $body + * + * @return void + */ + public function theAdministratorHasSentHttpMethodToOcsApiEndpointWithBody( + string $verb, + string $url, + ?TableNode $body + ):void { + $this->adminSendsHttpMethodToOcsApiEndpointWithBody( + $verb, + $url, + $body + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^the user sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with body$/ + * + * @param string $verb + * @param string $url + * @param TableNode $body + * + * @return void + */ + public function theUserSendsHTTPMethodToOcsApiEndpointWithBody(string $verb, string $url, TableNode $body):void { + $this->theUserSendsHTTPMethodToOcsApiEndpointWithBody( + $verb, + $url, + $body + ); + } + + /** + * @Given /^the user has sent HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with body$/ + * + * @param string $verb + * @param string $url + * @param TableNode $body + * + * @return void + */ + public function theUserHasSentHTTPMethodToOcsApiEndpointWithBody(string $verb, string $url, TableNode $body):void { + $this->theUserSendsHTTPMethodToOcsApiEndpointWithBody( + $verb, + $url, + $body + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When the administrator sends HTTP method :verb to OCS API endpoint :url with body using password :password + * + * @param string $verb + * @param string $url + * @param string $password + * @param TableNode $body + * + * @return void + */ + public function theAdministratorSendsHttpMethodToOcsApiWithBodyAndPassword( + string $verb, + string $url, + string $password, + TableNode $body + ):void { + $admin = $this->featureContext->getAdminUsername(); + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $admin, + $verb, + $url, + $body, + $password + ); + } + + /** + * @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with body using password "([^"]*)"$/ + * + * @param string $user + * @param string $verb + * @param string $url + * @param string $password + * @param TableNode $body + * + * @return void + */ + public function userSendsHTTPMethodToOcsApiEndpointWithBodyAndPassword( + string $user, + string $verb, + string $url, + string $password, + TableNode $body + ):void { + $this->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $verb, + $url, + $body, + $password + ); + } + + /** + * @When user :user requests these endpoints with :method using password :password about user :ofUser + * + * @param string $user + * @param string $method + * @param string $password + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userSendsRequestToTheseEndpointsWithOutBodyUsingPassword( + string $user, + string $method, + string $password, + string $ofUser, + TableNode $table + ):void { + $this->userSendsRequestToTheseEndpointsWithBodyUsingPassword( + $user, + $method, + null, + $password, + $ofUser, + $table + ); + } + + /** + * @When user :user requests these endpoints with :method including body :body using password :password about user :ofUser + * + * @param string|null $user + * @param string|null $method + * @param string|null $body + * @param string|null $password + * @param string|null $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userSendsRequestToTheseEndpointsWithBodyUsingPassword( + ?string $user, + ?string $method, + ?string $body, + ?string $password, + ?string $ofUser, + TableNode $table + ):void { + $user = $this->featureContext->getActualUsername($user); + $ofUser = $this->featureContext->getActualUsername($ofUser); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint'], ['destination']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $ofUser + ); + $header = []; + if (isset($row['destination'])) { + $header['Destination'] = $this->featureContext->substituteInLineCodes( + $this->featureContext->getBaseUrl() . $row['destination'], + $ofUser + ); + } + $this->featureContext->authContext->userRequestsURLWithUsingBasicAuth( + $user, + $row['endpoint'], + $method, + $password, + $body, + $header + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :user requests these endpoints with :method including body :body about user :ofUser + * + * @param string $user + * @param string $method + * @param string $body + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userSendsRequestToTheseEndpointsWithBody(string $user, string $method, string $body, string $ofUser, TableNode $table):void { + $header = []; + if ($method === 'MOVE' || $method === 'COPY') { + $header['Destination'] = '/path/to/destination'; + } + + $this->sendRequestToTheseEndpointsAsNormalUser( + $user, + $method, + $ofUser, + $table, + $body, + null, + $header, + ); + } + + /** + * @When user :user requests these endpoints with :method about user :ofUser + * + * @param string $user + * @param string $method + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userSendsRequestToTheseEndpointsWithOutBody(string $user, string $method, string $ofUser, TableNode $table):void { + $header = []; + if ($method === 'MOVE' || $method === 'COPY') { + $header['Destination'] = '/path/to/destination'; + } + + $this->sendRequestToTheseEndpointsAsNormalUser( + $user, + $method, + $ofUser, + $table, + null, + null, + $header, + ); + } + + /** + * @When /^user "([^"]*)" requests these endpoints with "([^"]*)" to (?:get|set) property "([^"]*)" about user "([^"]*)"$/ + * + * @param string $user + * @param string $method + * @param string $property + * @param string $ofUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userSendsRequestToTheseEndpointsWithProperty(string $user, string $method, string $property, string $ofUser, TableNode $table):void { + $this->sendRequestToTheseEndpointsAsNormalUser( + $user, + $method, + $ofUser, + $table, + null, + $property + ); + } + + /** + * @param string $user + * @param string $method + * @param string $ofUser + * @param TableNode $table + * @param string|null $body + * @param string|null $property + * @param Array|null $header + * + * @return void + * @throws Exception + */ + public function sendRequestToTheseEndpointsAsNormalUser( + string $user, + string $method, + string $ofUser, + TableNode $table, + ?string $body = null, + ?string $property = null, + ?array $header = null + ):void { + $user = $this->featureContext->getActualUsername($user); + $ofUser = $this->featureContext->getActualUsername($ofUser); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + if (!$body && $property) { + $body = $this->featureContext->getBodyForOCSRequest($method, $property); + } + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $ofUser + ); + $this->featureContext->authContext->userRequestsURLWithUsingBasicAuth( + $user, + $row['endpoint'], + $method, + $this->featureContext->getPasswordForUser($user), + $body, + $header + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :asUser requests these endpoints with :method including body :body using the password of user :user + * + * @param string $asUser + * @param string $method + * @param string|null $body + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsTheseEndpointsWithBodyUsingThePasswordOfUser(string $asUser, string $method, ?string $body, string $user, TableNode $table):void { + $asUser = $this->featureContext->getActualUsername($asUser); + $userRenamed = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumns($table, ['endpoint']); + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->emptyLastOCSStatusCodesArray(); + foreach ($table->getHash() as $row) { + $row['endpoint'] = $this->featureContext->substituteInLineCodes( + $row['endpoint'], + $userRenamed + ); + $this->featureContext->authContext->userRequestsURLWithUsingBasicAuth( + $asUser, + $row['endpoint'], + $method, + $this->featureContext->getPasswordForUser($user), + $body + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :asUser requests these endpoints with :method using the password of user :user + * + * @param string $asUser + * @param string $method + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRequestsTheseEndpointsWithoutBodyUsingThePasswordOfUser(string $asUser, string $method, string $user, TableNode $table):void { + $this->userRequestsTheseEndpointsWithBodyUsingThePasswordOfUser( + $asUser, + $method, + null, + $user, + $table + ); + } + + /** + * @Then /^the OCS status code should be "([^"]*)"$/ + * + * @param string $statusCode + * @param string $message + * + * @return void + * @throws Exception + */ + public function theOCSStatusCodeShouldBe(string $statusCode, $message = ""):void { + $statusCodes = explode(",", $statusCode); + $responseStatusCode = $this->getOCSResponseStatusCode( + $this->featureContext->getResponse() + ); + if (\is_array($statusCodes)) { + if ($message === "") { + $message = "OCS status code is not any of the expected values " . \implode(",", $statusCodes) . " got " . $responseStatusCode; + } + Assert::assertContainsEquals( + $responseStatusCode, + $statusCodes, + $message + ); + $this->featureContext->emptyLastOCSStatusCodesArray(); + } else { + if ($message === "") { + $message = "OCS status code is not the expected value " . $statusCodes . " got " . $responseStatusCode; + } + + Assert::assertEquals( + $statusCodes, + $responseStatusCode, + $message + ); + } + } + + /** + * @Then /^the OCS status code should be "([^"]*)" or "([^"]*)"$/ + * + * @param string $statusCode1 + * @param string $statusCode2 + * + * @return void + * @throws Exception + */ + public function theOcsStatusCodeShouldBeOr(string $statusCode1, string $statusCode2):void { + $this->theOCSStatusCodeShouldBe( + $statusCode1 . "," . $statusCode2 + ); + } + + /** + * Check the text in an OCS status message + * + * @Then /^the OCS status message should be "([^"]*)"$/ + * @Then /^the OCS status message should be "([^"]*)" in language "([^"]*)"$/ + * + * @param string $statusMessage + * @param string|null $language + * + * @return void + */ + public function theOCSStatusMessageShouldBe(string $statusMessage, ?string $language = null):void { + $language = TranslationHelper::getLanguage($language); + $statusMessage = $this->getActualStatusMessage($statusMessage, $language); + + Assert::assertEquals( + $statusMessage, + $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ), + 'Unexpected OCS status message :"' . $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ) . '" in response' + ); + } + + /** + * Check the text in an OCS status message + * + * @Then /^the OCS status message about user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $statusMessage + * + * @return void + */ + public function theOCSStatusMessageAboutUserShouldBe(string $user, string $statusMessage):void { + $user = \strtolower($this->featureContext->getActualUsername($user)); + $statusMessage = $this->featureContext->substituteInLineCodes( + $statusMessage, + $user + ); + Assert::assertEquals( + $statusMessage, + $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ), + 'Unexpected OCS status message :"' . $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ) . '" in response' + ); + } + + /** + * Check the text in an OCS status message. + * Use this step form if the expected text contains double quotes, + * single quotes and other content that theOCSStatusMessageShouldBe() + * cannot handle. + * + * After the step, write the expected text in PyString form like: + * + * """ + * File "abc.txt" can't be shared due to reason "xyz" + * """ + * + * @Then /^the OCS status message should be:$/ + * + * @param PyStringNode $statusMessage + * + * @return void + */ + public function theOCSStatusMessageShouldBePyString( + PyStringNode $statusMessage + ):void { + Assert::assertEquals( + $statusMessage->getRaw(), + $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ), + 'Unexpected OCS status message: "' . $this->getOCSResponseStatusMessage( + $this->featureContext->getResponse() + ) . '" in response' + ); + } + + /** + * Parses the xml answer to get ocs response which doesn't match with + * http one in v1 of the api. + * + * @param ResponseInterface $response + * + * @return string + * @throws Exception + */ + public function getOCSResponseStatusCode(ResponseInterface $response):string { + $responseXml = $this->featureContext->getResponseXml($response, __METHOD__); + if (isset($responseXml->meta[0], $responseXml->meta[0]->statuscode)) { + return (string) $responseXml->meta[0]->statuscode; + } + throw new Exception( + "No OCS status code found in responseXml" + ); + } + + /** + * Parses the xml answer to get ocs response message which doesn't match with + * http one in v1 of the api. + * + * @param ResponseInterface $response + * + * @return string + */ + public function getOCSResponseStatusMessage(ResponseInterface $response):string { + return (string) $this->featureContext->getResponseXml($response, __METHOD__)->meta[0]->message; + } + + /** + * convert status message in the desired language + * + * @param string $statusMessage + * @param string|null $language + * + * @return string + */ + public function getActualStatusMessage(string $statusMessage, ?string $language = null):string { + if ($language !== null) { + $multiLingualMessage = \json_decode( + \file_get_contents(__DIR__ . "/../../fixtures/multiLanguageErrors.json"), + true + ); + + if (isset($multiLingualMessage[$statusMessage][$language])) { + $statusMessage = $multiLingualMessage[$statusMessage][$language]; + } + } + return $statusMessage; + } + + /** + * check if the HTTP status code and the OCS status code indicate that the request was successful + * this function is aware of the currently used OCS version + * + * @param string|null $message + * + * @return void + * @throws Exception + */ + public function assertOCSResponseIndicatesSuccess(?string $message = ""):void { + $this->featureContext->theHTTPStatusCodeShouldBe('200', $message); + if ($this->featureContext->getOcsApiVersion() === 1) { + $this->theOCSStatusCodeShouldBe('100', $message); + } else { + $this->theOCSStatusCodeShouldBe('200', $message); + } + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/OccContext.php b/tests/acceptance/features/bootstrap/OccContext.php new file mode 100644 index 00000000000..53d62081ce6 --- /dev/null +++ b/tests/acceptance/features/bootstrap/OccContext.php @@ -0,0 +1,3748 @@ + + * @copyright Copyright (c) 2019, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Exception\GuzzleException; +use PHPUnit\Framework\Assert; +use TestHelpers\OcisHelper; +use TestHelpers\SetupHelper; +use Behat\Gherkin\Node\PyStringNode; + +require_once 'bootstrap.php'; + +/** + * Occ context for test steps that test occ commands + */ +class OccContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var ImportedCertificates + */ + private $importedCertificates = []; + + /** + * + * @var RemovedCertificates + */ + private $removedCertificates = []; + + /** + * @var string lastDeletedJobId + */ + private $lastDeletedJobId; + + /** + * The code to manage dav.enable.tech_preview was used in 10.4/10.3 + * The use of the steps to enable/disable it has been removed from the + * feature files. But the infrastructure has been left here, as a similar + * thing might likely happen in the future. + * + * @var boolean + */ + private $doTechPreview = false; + + /** + * @var boolean techPreviewEnabled + */ + private $techPreviewEnabled = false; + + /** + * @var string initialTechPreviewStatus + */ + private $initialTechPreviewStatus; + + /** + * @return boolean + */ + public function isTechPreviewEnabled():bool { + return $this->techPreviewEnabled; + } + + /** + * @return boolean + * @throws Exception + */ + public function enableDAVTechPreview():bool { + if ($this->doTechPreview) { + if (!$this->isTechPreviewEnabled()) { + $this->addSystemConfigKeyUsingTheOccCommand( + "dav.enable.tech_preview", + "true", + "boolean" + ); + $this->techPreviewEnabled = true; + return true; + } + } + return false; + } + + /** + * @return boolean true if delete-system-config-key was done + * @throws Exception + */ + public function disableDAVTechPreview():bool { + if ($this->doTechPreview) { + $this->deleteSystemConfigKeyUsingTheOccCommand( + "dav.enable.tech_preview" + ); + $this->techPreviewEnabled = false; + return true; + } + return false; + } + + /** + * @param string $cmd + * + * @return void + * @throws Exception + */ + public function invokingTheCommand(string $cmd):void { + $this->featureContext->setOccLastCode( + $this->featureContext->runOcc([$cmd]) + ); + } + + /** + * @param string $path + * + * @return void + * @throws Exception + */ + public function importSecurityCertificateFromFileInTemporaryStorage(string $path):void { + $this->invokingTheCommand("security:certificates:import " . TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$path"); + if ($this->featureContext->getExitStatusCodeOfOccCommand() === 0) { + $pathComponents = \explode("/", $path); + $certificate = \end($pathComponents); + \array_push($this->importedCertificates, $certificate); + } + } + + /** + * @param string $cmd + * @param string $envVariableName + * @param string $envVariableValue + * + * @return void + * @throws Exception + */ + public function invokingTheCommandWithEnvVariable( + string $cmd, + string $envVariableName, + string $envVariableValue + ):int { + $args = [$cmd]; + return( + $this->featureContext->runOccWithEnvVariables( + $args, + [$envVariableName => $envVariableValue] + ) + ); + } + + /** + * @param string $mode + * + * @return void + * @throws Exception + */ + public function changeBackgroundJobsModeUsingTheOccCommand(string $mode):void { + $this->invokingTheCommand("background:$mode"); + } + + /** + * @param string $mountPoint + * @param boolean $setting + * + * @return void + * @throws Exception + */ + public function setExtStorageReadOnlyUsingTheOccCommand(string $mountPoint, bool $setting = true):void { + $command = "files_external:option"; + + $mountId = $this->featureContext->getStorageId($mountPoint); + + $key = "read_only"; + + if ($setting) { + $value = "1"; + } else { + $value = "0"; + } + + $this->invokingTheCommand( + "$command $mountId $key $value" + ); + } + + /** + * @param string $mountPoint + * @param boolean $enable + * + * @return void + * @throws Exception + */ + public function setExtStorageSharingUsingTheOccCommand(string $mountPoint, bool $enable = true):void { + $command = "files_external:option"; + + $mountId = $this->featureContext->getStorageId($mountPoint); + + $key = "enable_sharing"; + + $value = $enable ? "true" : "false"; + + $this->invokingTheCommand( + "$command $mountId $key $value" + ); + } + + /** + * @param string $mountPoint + * @param string $setting "never" (switch it off) otherwise "Once every direct access" + * + * @return void + * @throws Exception + */ + public function setExtStorageCheckChangesUsingTheOccCommand(string $mountPoint, string $setting):void { + $command = "files_external:option"; + + $mountId = $this->featureContext->getStorageId($mountPoint); + + $key = "filesystem_check_changes"; + + if ($setting === "never") { + $value = "0"; + } else { + $value = "1"; + } + + $this->invokingTheCommand( + "$command $mountId $key $value" + ); + } + + /** + * @return void + * @throws Exception + */ + public function scanFileSystemForAllUsersUsingTheOccCommand():void { + $this->invokingTheCommand( + "files:scan --all" + ); + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function scanFileSystemForAUserUsingTheOccCommand(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $this->invokingTheCommand( + "files:scan $user" + ); + } + + /** + * @param string $path + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function scanFileSystemPathUsingTheOccCommand(string $path, ?string $user = null):void { + $path = $this->featureContext->substituteInLineCodes( + $path, + $user + ); + $this->invokingTheCommand( + "files:scan --path='$path'" + ); + } + + /** + * @param string $group + * + * @return void + * @throws Exception + */ + public function scanFileSystemForAGroupUsingTheOccCommand(string $group):void { + $this->invokingTheCommand( + "files:scan --group=$group" + ); + } + + /** + * @param string $groups + * + * @return void + * @throws Exception + */ + public function scanFileSystemForGroupsUsingTheOccCommand(string $groups):void { + $this->invokingTheCommand( + "files:scan --groups=$groups" + ); + } + + /** + * @param string $mount + * + * @return void + */ + public function createLocalStorageMountUsingTheOccCommand(string $mount):void { + $result = SetupHelper::createLocalStorageMount( + $mount, + $this->featureContext->getStepLineRef() + ); + $storageId = $result['storageId']; + if (!is_numeric($storageId)) { + throw new Exception( + __METHOD__ . " storageId '$storageId' is not numeric" + ); + } + $this->featureContext->setResultOfOccCommand($result); + $this->featureContext->addStorageId($mount, (int) $storageId); + } + + /** + * @param string $key + * @param string $value + * @param string $app + * + * @return void + * @throws Exception + */ + public function addConfigKeyWithValueInAppUsingTheOccCommand(string $key, string $value, string $app):void { + $this->invokingTheCommand( + "config:app:set --value ${value} ${app} ${key}" + ); + } + + /** + * @param string $key + * @param string $app + * + * @return void + * @throws Exception + */ + public function deleteConfigKeyOfAppUsingTheOccCommand(string $key, string $app):void { + $this->invokingTheCommand( + "config:app:delete ${app} ${key}" + ); + } + + /** + * @param string $key + * @param string $value + * @param string $type + * + * @return void + * @throws Exception + */ + public function addSystemConfigKeyUsingTheOccCommand( + string $key, + string $value, + string $type = "string" + ):void { + $this->invokingTheCommand( + "config:system:set --value '${value}' --type ${type} ${key}" + ); + } + + /** + * @param string $key + * + * @return void + * @throws Exception + */ + public function deleteSystemConfigKeyUsingTheOccCommand(string $key):void { + $this->invokingTheCommand( + "config:system:delete ${key}" + ); + } + + /** + * + * @return void + * @throws Exception + */ + public function cleanOrphanedRemoteStoragesUsingOccCommand():void { + $this->invokingTheCommand( + "sharing:cleanup-remote-storages" + ); + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function emptyTrashBinOfUserUsingOccCommand(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $this->invokingTheCommand( + "trashbin:cleanup $user" + ); + } + + /** + * Create a calendar for given user with given calendar name + * + * @param string $user + * @param string $calendarName + * + * @return void + * @throws Exception + */ + public function createCalendarForUserUsingOccCommand(string $user, string $calendarName):void { + $this->invokingTheCommand("dav:create-calendar $user $calendarName"); + } + + /** + * Create an address book for given user with given address book name + * + * @param string $user + * @param string $addressBookName + * + * @return void + * @throws Exception + */ + public function createAnAddressBookForUserUsingOccCommand(string $user, string $addressBookName):void { + $this->invokingTheCommand("dav:create-addressbook $user $addressBookName"); + } + + /** + * @return void + * @throws Exception + */ + public function getAllJobsInBackgroundQueueUsingOccCommand():void { + $this->invokingTheCommand( + "background:queue:status" + ); + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function deleteAllVersionsForUserUsingOccCommand(string $user = ""): void { + $this->invokingTheCommand( + "versions:cleanup $user" + ); + } + + /** + * @param string $users space-separated usernames. E.g. "Alice Brian" (without ) + * + * @return void + * @throws Exception + */ + public function deleteAllVersionsForMultipleUsersUsingOccCommand(string $users): void { + $this->deleteAllVersionsForUserUsingOccCommand($users); + } + + /** + * @return void + * @throws Exception + */ + public function deleteAllVersionsForAllUsersUsingOccCommand(): void { + $this->deleteAllVersionsForUserUsingOccCommand(); + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function deleteExpiredVersionsForUserUsingOccCommand(string $user = ""): void { + $this->invokingTheCommand( + "versions:expire $user" + ); + } + + /** + * @param string $users space-separated usernames. E.g. "Alice Brian" (without ) + * + * @return void + * @throws Exception + */ + public function deleteExpiredVersionsForMultipleUsersUsingOccCommand(string $users): void { + $this->deleteExpiredVersionsForUserUsingOccCommand($users); + } + + /** + * @return void + * @throws Exception + */ + public function deleteExpiredVersionsForAllUsersUsingOccCommand(): void { + $this->deleteExpiredVersionsForUserUsingOccCommand(); + } + + /** + * @param string $job + * + * @return void + * @throws Exception + */ + public function deleteLastBackgroundJobUsingTheOccCommand(string $job):void { + $match = $this->getLastJobIdForJob($job); + if ($match === false) { + throw new Exception("Couldn't find jobId for given job: $job"); + } + $this->invokingTheCommand( + "background:queue:delete $match" + ); + $this->lastDeletedJobId = $match; + } + + /** + * List created local storage mount + * + * @return void + * @throws Exception + */ + public function listLocalStorageMount():void { + $this->invokingTheCommand('files_external:list --output=json'); + } + + /** + * @When the administrator lists all local storage mount points using the occ command + * + * List created local storage mount with --short + * + * @return void + * @throws Exception + */ + public function listLocalStorageMountShort():void { + $this->invokingTheCommand('files_external:list --short --output=json'); + } + + /** + * List created local storage mount with --mount-options + * + * @return void + * @throws Exception + */ + public function listLocalStorageMountOptions():void { + $this->invokingTheCommand('files_external:list --mount-options --output=json'); + } + + /** + * List available backends + * + * @return void + * @throws Exception + */ + public function listAvailableBackends():void { + $this->invokingTheCommand('files_external:backends --output=json'); + } + + /** + * List available backends of type + * + * @param String $type + * + * @return void + * @throws Exception + */ + public function listAvailableBackendsOfType(string $type):void { + $this->invokingTheCommand('files_external:backends ' . $type . ' --output=json'); + } + + /** + * List backend of type + * + * @param String $type + * @param String $backend + * + * @return void + * @throws Exception + */ + public function listBackendOfType(string $type, string $backend):void { + $this->invokingTheCommand('files_external:backends ' . $type . ' ' . $backend . ' --output=json'); + } + + /** + * List created local storage mount with --show-password + * + * @return void + * @throws Exception + */ + public function listLocalStorageShowPassword():void { + $this->invokingTheCommand('files_external:list --show-password --output=json'); + } + + /** + * @When the administrator enables DAV tech_preview + * + * @return void + * @throws Exception + */ + public function theAdministratorEnablesDAVTechPreview():void { + $this->enableDAVTechPreview(); + } + + /** + * @Given the administrator has enabled DAV tech_preview + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEnabledDAVTechPreview():void { + if ($this->enableDAVTechPreview()) { + $this->theCommandShouldHaveBeenSuccessful(); + } + } + + /** + * @When /^the administrator invokes occ command "([^"]*)"$/ + * + * @param string $cmd + * + * @return void + * @throws Exception + */ + public function theAdministratorInvokesOccCommand(string $cmd):void { + $this->invokingTheCommand($cmd); + } + + /** + * @When /^the administrator invokes occ command "([^"]*)" for user "([^"]*)"$/ + * + * @param string $cmd + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorInvokesOccCommandForUser(string $cmd, string $user):void { + $user = $this->featureContext->getActualUsername($user); + $cmd = $this->featureContext->substituteInLineCodes( + $cmd, + $user + ); + $this->invokingTheCommand($cmd); + } + + /** + * @Given /^the administrator has invoked occ command "([^"]*)"$/ + * + * @param string $cmd + * + * @return void + * @throws Exception + */ + public function theAdministratorHasInvokedOccCommand(string $cmd):void { + $this->invokingTheCommand($cmd); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @Given the administrator has selected master key encryption type using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSelectedMasterKeyEncryptionTypeUsingTheOccCommand():void { + $this->featureContext->runOcc(['encryption:select-encryption-type', "masterkey --yes"]); + } + + /** + * @When the administrator imports security certificate from file :filename in temporary storage on the system under test + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function theAdministratorImportsSecurityCertificateFromFile(string $filename):void { + $this->importSecurityCertificateFromFileInTemporaryStorage($filename); + } + + /** + * @Given the administrator has imported security certificate from file :filename in temporary storage on the system under test + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function theAdministratorHasImportedSecurityCertificateFromFile(string $filename):void { + $this->importSecurityCertificateFromFileInTemporaryStorage($filename); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator removes the security certificate :certificate + * + * @param string $certificate + * + * @return void + * @throws Exception + */ + public function theAdministratorRemovesTheSecurityCertificate(string $certificate):void { + $this->invokingTheCommand("security:certificates:remove " . $certificate); + \array_push($this->removedCertificates, $certificate); + } + + /** + * @When /^the administrator invokes occ command "([^"]*)" with environment variable "([^"]*)" set to "([^"]*)"$/ + * + * @param string $cmd + * @param string $envVariableName + * @param string $envVariableValue + * + * @return void + * @throws Exception + */ + public function theAdministratorInvokesOccCommandWithEnvironmentVariable( + string $cmd, + string $envVariableName, + string $envVariableValue + ):void { + $this->featureContext->setOccLastCode( + $this->invokingTheCommandWithEnvVariable( + $cmd, + $envVariableName, + $envVariableValue + ) + ); + } + + /** + * @Given /^the administrator has invoked occ command "([^"]*)" with environment variable "([^"]*)" set to "([^"]*)"$/ + * + * @param string $cmd + * @param string $envVariableName + * @param string $envVariableValue + * + * @return void + * @throws Exception + */ + public function theAdministratorHasInvokedOccCommandWithEnvironmentVariable( + string $cmd, + string $envVariableName, + string $envVariableValue + ):void { + $this->invokingTheCommandWithEnvVariable( + $cmd, + $envVariableName, + $envVariableValue + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator runs upgrade routines on local server using the occ command + * + * @return void + */ + public function theAdministratorRunsUpgradeRoutinesOnLocalServerUsingTheOccCommand():void { + \system("./occ upgrade", $status); + if ($status !== 0) { + // if the above command fails make sure to turn off maintenance mode + \system("./occ maintenance:mode --off"); + } + } + + /** + * @Given the administrator has decrypted everything + * + * @return void + * @throws Exception + */ + public function theAdministratorHasDecryptedEverything():void { + $this->theAdministratorRunsEncryptionDecryptAllUsingTheOccCommand(); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator disables encryption using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorDisablesEncryptionUsingTheOccCommand():void { + $this->invokingTheCommand("encryption:disable"); + } + + /** + * @When the administrator runs encryption decrypt all using the occ command + * + * @return void + */ + public function theAdministratorRunsEncryptionDecryptAllUsingTheOccCommand():void { + \system("./occ maintenance:singleuser --on"); + \system("./occ encryption:decrypt-all -c yes", $status); + + $this->featureContext->setResultOfOccCommand(["code" => $status, "stdOut" => "", "stdErr" => ""]); + \system("./occ maintenance:singleuser --off"); + } + + /** + * @return bool + */ + public function theOccCommandExitStatusWasSuccess():bool { + return ($this->featureContext->getExitStatusCodeOfOccCommand() === 0); + } + + /** + * @Then /^the command should have been successful$/ + * + * @return void + * @throws Exception + */ + public function theCommandShouldHaveBeenSuccessful():void { + $exceptions = $this->featureContext->findExceptions(); + $exitStatusCode = $this->featureContext->getExitStatusCodeOfOccCommand(); + if ($exitStatusCode !== 0) { + $msg = "The command was not successful, exit code was " . + $exitStatusCode . ".\n" . + "stdOut was: '" . + $this->featureContext->getStdOutOfOccCommand() . "'\n" . + "stdErr was: '" . + $this->featureContext->getStdErrOfOccCommand() . "'\n"; + if (!empty($exceptions)) { + $msg .= ' Exceptions: ' . \implode(', ', $exceptions); + } + if ($exitStatusCode === null) { + $msg = "The occ command did not run "; + } + throw new Exception($msg); + } elseif (!empty($exceptions)) { + $msg = 'The command was successful but triggered exceptions: ' + . \implode(', ', $exceptions); + throw new Exception($msg); + } + } + + /** + * @Then /^the command should have failed with exit code ([0-9]+)$/ + * + * @param int $exitCode + * + * @return void + * @throws Exception + */ + public function theCommandFailedWithExitCode(int $exitCode):void { + $exitStatusCode = $this->featureContext->getExitStatusCodeOfOccCommand(); + Assert::assertEquals( + (int) $exitCode, + $exitStatusCode, + "The command was expected to fail with exit code $exitCode but got {$exitStatusCode}" + ); + } + + /** + * @Then /^the command output should be the text ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $text + * + * @return void + * @throws Exception + */ + public function theCommandOutputShouldBeTheText(string $text):void { + $text = \trim($text, $text[0]); + $commandOutput = \trim($this->featureContext->getStdOutOfOccCommand()); + Assert::assertEquals( + $text, + $commandOutput, + "The command output did not match the expected text on stdout '$text'\n" . + "The command output on stdout was:\n" . + $commandOutput + ); + } + + /** + * @Then /^the command should have failed with exception text "([^"]*)"$/ + * + * @param string $exceptionText + * + * @return void + * @throws Exception + */ + public function theCommandFailedWithExceptionText(string $exceptionText):void { + $exceptions = $this->featureContext->findExceptions(); + Assert::assertNotEmpty( + $exceptions, + 'The command did not throw any exceptions' + ); + + Assert::assertContains( + $exceptionText, + $exceptions, + "The command did not throw any exception with the text '$exceptionText'" + ); + } + + /** + * @Then /^the command output should contain the text ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $text + * + * @return void + * @throws Exception + */ + public function theCommandOutputContainsTheText(string $text):void { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + $text = \trim($text, $text[0]); + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $lines = SetupHelper::findLines( + $commandOutput, + $text + ); + Assert::assertGreaterThanOrEqual( + 1, + \count($lines), + "The command output did not contain the expected text on stdout '$text'\n" . + "The command output on stdout was:\n" . + $commandOutput + ); + } + + /** + * @Then /^the command output should contain the text ((?:'[^']*')|(?:"[^"]*")) about user "([^"]*)"$/ + * + * @param string $text + * @param string $user + * + * @return void + * @throws Exception + */ + public function theCommandOutputContainsTheTextAboutUser(string $text, string $user):void { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + $text = \trim($text, $text[0]); + $text = $this->featureContext->substituteInLineCodes( + $text, + $user + ); + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $lines = SetupHelper::findLines( + $commandOutput, + $text + ); + Assert::assertGreaterThanOrEqual( + 1, + \count($lines), + "The command output did not contain the expected text on stdout '$text'\n" . + "The command output on stdout was:\n" . + $commandOutput + ); + } + + /** + * @Then /^the command error output should contain the text ((?:'[^']*')|(?:"[^"]*"))$/ + * + * @param string $text + * + * @return void + * @throws Exception + */ + public function theCommandErrorOutputContainsTheText(string $text):void { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + $text = \trim($text, $text[0]); + $commandOutput = $this->featureContext->getStdErrOfOccCommand(); + $lines = SetupHelper::findLines( + $commandOutput, + $text + ); + Assert::assertGreaterThanOrEqual( + 1, + \count($lines), + "The command output did not contain the expected text on stderr '$text'\n" . + "The command output on stderr was:\n" . + $commandOutput + ); + } + + /** + * @Then the occ command JSON output should be empty + * + * @return void + */ + public function theOccCommandJsonOutputShouldNotReturnAnyData():void { + Assert::assertEquals( + \trim($this->featureContext->getStdOutOfOccCommand()), + "[]", + "Expected command output to be '[]' but got '" + . \trim($this->featureContext->getStdOutOfOccCommand()) + . "'" + ); + Assert::assertEmpty( + $this->featureContext->getStdErrOfOccCommand(), + "Expected occ command error to be empty but got '" + . $this->featureContext->getStdErrOfOccCommand() + . "'" + ); + } + + /** + * @Then :noOfOrphanedShare orphaned remote storage should have been cleared + * + * @param int $noOfOrphanedShare + * + * @return void + * @throws Exception + */ + public function theOrphanedRemoteStorageShouldBeCleared(int $noOfOrphanedShare):void { + $this->theCommandShouldHaveBeenSuccessful(); + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + // removing blank lines + $commandOutput = implode("\n", array_filter(explode("\n", $commandOutput))); + // splitting strings based on newline into an array + $outputLines = preg_split("/\r\n|\n|\r/", $commandOutput); + // first line of command output contains total remote storage scanned + $totalRemoteStorage = (int) filter_var($outputLines[0], FILTER_SANITIZE_NUMBER_INT); + echo "totalremotestorage: $totalRemoteStorage"; + // calculating total no of shares deleted from remote storage: first getting total length of the array + // then minus 2 for first two lines of scan info message + // then divide by 2 because each share delete has message of two line + $totalSharesDeleted = ((\count($outputLines) - 2) / 2) / $totalRemoteStorage; + Assert::assertEquals( + $noOfOrphanedShare, + $totalSharesDeleted, + "The command was expected to delete '$noOfOrphanedShare' orphaned shares but only deleted '$totalSharesDeleted' orphaned shares." + ); + } + + /** + * @Given the administrator has set the default folder for received shares to :folder + * + * @param string $folder + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetTheDefaultFolderForReceivedSharesTo(string $folder):void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // The default folder for received shares is already "Shares" on OCIS and REVA. + // If the step is asking for a different folder, then fail. + // Otherwise just return - the setting is already done by default. + Assert::assertEquals( + "Shares", + \trim($folder, "/"), + __METHOD__ . " tried to set the default folder for received shares to $folder but that is not possible on OCIS" + ); + return; + } + $this->addSystemConfigKeyUsingTheOccCommand( + "share_folder", + $folder + ); + } + + /** + * @When the administrator has set depth_infinity_allowed to :depth_infinity_allowed + * + * @param int $depthInfinityAllowed + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetDepthInfinityAllowedTo($depthInfinityAllowed) { + $depthInfinityAllowedString = (string) $depthInfinityAllowed; + $this->addSystemConfigKeyUsingTheOccCommand( + "dav.propfind.depth_infinity", + $depthInfinityAllowedString + ); + if ($depthInfinityAllowedString === "0") { + $this->featureContext->davPropfindDepthInfinityDisabled(); + } else { + $this->featureContext->davPropfindDepthInfinityEnabled(); + } + } + + /** + * @Given the administrator has set the mail smtpmode to :smtpmode + * + * @param string $smtpmode + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetTheMailSmtpmodeTo(string $smtpmode):void { + $this->addSystemConfigKeyUsingTheOccCommand( + "mail_smtpmode", + $smtpmode + ); + } + + /** + * @When the administrator sets the log level to :level using the occ command + * + * @param string $level + * + * @return void + * @throws Exception + */ + public function theAdministratorSetsLogLevelUsingTheOccCommand(string $level):void { + $this->invokingTheCommand( + "log:manage --level $level" + ); + } + + /** + * @When the administrator sets the timezone to :timezone using the occ command + * + * @param string $timezone + * + * @return void + * @throws Exception + */ + public function theAdministratorSetsTimeZoneUsingTheOccCommand(string $timezone):void { + $this->invokingTheCommand( + "log:manage --timezone $timezone" + ); + } + + /** + * @When the administrator sets the backend to :backend using the occ command + * + * @param string $backend + * + * @return void + * @throws Exception + */ + public function theAdministratorSetsBackendUsingTheOccCommand(string $backend):void { + $this->invokingTheCommand( + "log:manage --backend $backend" + ); + } + + /** + * @When the administrator enables the ownCloud backend using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorEnablesOwnCloudBackendUsingTheOccCommand():void { + $this->invokingTheCommand( + "log:owncloud --enable" + ); + } + + /** + * @When the administrator sets the log file path to :path using the occ command + * + * @param string $path + * + * @return void + * @throws Exception + */ + public function theAdministratorSetsLogFilePathUsingTheOccCommand(string $path):void { + $this->invokingTheCommand( + "log:owncloud --file $path" + ); + } + + /** + * @When the administrator sets the log rotate file size to :size using the occ command + * + * @param string $size + * + * @return void + * @throws Exception + */ + public function theAdministratorSetsLogRotateFileSizeUsingTheOccCommand(string $size):void { + $this->invokingTheCommand( + "log:owncloud --rotate-size $size" + ); + } + + /** + * @Then the command output should be: + * + * @param PyStringNode $content + * + * @return void + * @throws Exception + */ + public function theCommandOutputShouldBe(PyStringNode $content):void { + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + // removing blank lines + $commandOutput = \implode("\n", \array_filter(\explode("\n", $commandOutput))); + $content = \implode("\n", \array_filter(\explode("\n", $content->getRaw()))); + Assert::assertEquals( + $content, + $commandOutput, + "The command output was expected to be '$content' but got '$commandOutput'" + ); + } + + /** + * @When the administrator changes the background jobs mode to :mode using the occ command + * + * @param string $mode + * + * @return void + * @throws Exception + */ + public function theAdministratorChangesTheBackgroundJobsModeTo(string $mode):void { + $this->changeBackgroundJobsModeUsingTheOccCommand($mode); + } + + /** + * @Given the administrator has changed the background jobs mode to :mode + * + * @param string $mode + * + * @return void + * @throws Exception + */ + public function theAdministratorHasChangedTheBackgroundJobsModeTo(string $mode):void { + $this->changeBackgroundJobsModeUsingTheOccCommand($mode); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator sets the external storage :mountPoint to read-only using the occ command + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminSetsTheExtStorageToReadOnly(string $mountPoint):void { + $this->setExtStorageReadOnlyUsingTheOccCommand($mountPoint); + } + + /** + * @Given the administrator has set the external storage :mountPoint to read-only + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminHasSetTheExtStorageToReadOnly(string $mountPoint):void { + $this->setExtStorageReadOnlyUsingTheOccCommand($mountPoint); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @Given the administrator has set the external storage :mountPoint to sharing + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminHasSetTheExtStorageToSharing(string $mountPoint):void { + $this->setExtStorageSharingUsingTheOccCommand($mountPoint); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When /^the administrator (enables|disables) sharing for the external storage "([^"]*)" using the occ command$/ + * + * @param string $action + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminDisablesSharingForTheExtStorage(string $action, string $mountPoint):void { + $this->setExtStorageSharingUsingTheOccCommand($mountPoint, $action === "enables"); + } + + /** + * @When the administrator sets the external storage :mountPoint to be never scanned automatically using the occ command + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminSetsTheExtStorageToBeNeverScannedAutomatically(string $mountPoint):void { + $this->setExtStorageCheckChangesUsingTheOccCommand($mountPoint, "never"); + } + + /** + * @Given the administrator has set the external storage :mountPoint to be never scanned automatically + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function theAdminHasSetTheExtStorageToBeNeverScannedAutomatically(string $mountPoint):void { + $this->setExtStorageCheckChangesUsingTheOccCommand($mountPoint, "never"); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator scans the filesystem for all users using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorScansTheFilesystemForAllUsersUsingTheOccCommand():void { + $this->scanFileSystemForAllUsersUsingTheOccCommand(); + } + + /** + * @Given the administrator has scanned the filesystem for all users + * + * @return void + * @throws Exception + */ + public function theAdministratorHasScannedTheFilesystemForAllUsersUsingTheOccCommand():void { + $this->scanFileSystemForAllUsersUsingTheOccCommand(); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator scans the filesystem for user :user using the occ command + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorScansTheFilesystemForUserUsingTheOccCommand(string $user):void { + $this->scanFileSystemForAUserUsingTheOccCommand($user); + } + + /** + * @Given the administrator has scanned the filesystem for user :user + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorHasScannedTheFilesystemForUserUsingTheOccCommand(string $user):void { + $this->scanFileSystemForAUserUsingTheOccCommand($user); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator scans the filesystem in path :path of user :user using the occ command + * + * @param string $path + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorScansTheFilesystemInPathUsingTheOccCommand(string $path, string $user):void { + $user = $this->featureContext->getActualUsername($user); + $this->scanFileSystemPathUsingTheOccCommand($path, $user); + } + + /** + * @Given the administrator scans the filesystem in path :path + * + * @param string $path + * + * @return void + * @throws Exception + */ + public function theAdministratorHasScannedTheFilesystemInPathUsingTheOccCommand(string $path):void { + $this->scanFileSystemPathUsingTheOccCommand($path); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator scans the filesystem for group :group using the occ command + * + * Used to test the --group option of the files:scan command + * + * @param string $group a single group name + * + * @return void + * @throws Exception + */ + public function theAdministratorScansTheFilesystemForGroupUsingTheOccCommand(string $group):void { + $this->scanFileSystemForAGroupUsingTheOccCommand($group); + } + + /** + * @Given the administrator has scanned the filesystem for group :group + * + * Used to test the --group option of the files:scan command + * + * @param string $group a single group name + * + * @return void + * @throws Exception + */ + public function theAdministratorHasScannedTheFilesystemForGroupUsingTheOccCommand(string $group):void { + $this->scanFileSystemForAGroupUsingTheOccCommand($group); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator scans the filesystem for groups list :groups using the occ command + * + * Used to test the --groups option of the files:scan command + * + * @param string $groups a comma-separated list of group names + * + * @return void + * @throws Exception + */ + public function theAdministratorScansTheFilesystemForGroupsUsingTheOccCommand(string $groups):void { + $this->scanFileSystemForGroupsUsingTheOccCommand($groups); + } + + /** + * @Given the administrator has scanned the filesystem for groups list :groups + * + * Used to test the --groups option of the files:scan command + * + * @param string $groups a comma-separated list of group names + * + * @return void + * @throws Exception + */ + public function theAdministratorHasScannedTheFilesystemForGroupsUsingTheOccCommand(string $groups):void { + $this->scanFileSystemForGroupsUsingTheOccCommand($groups); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator cleanups the filesystem for all users using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorCleanupsTheFilesystemForAllUsersUsingTheOccCommand():void { + $this->invokingTheCommand( + "files:cleanup" + ); + } + + /** + * @When the administrator creates the local storage mount :mount using the occ command + * + * @param string $mount + * + * @return void + */ + public function theAdministratorCreatesTheLocalStorageMountUsingTheOccCommand(string $mount):void { + $this->createLocalStorageMountUsingTheOccCommand($mount); + } + + /** + * @Given the administrator has created the local storage mount :mount + * + * @param string $mount + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCreatedTheLocalStorageMountUsingTheOccCommand(string $mount):void { + $this->createLocalStorageMountUsingTheOccCommand($mount); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @param string $action + * @param string $userOrGroup + * @param string $userOrGroupName + * @param string $mountName + * + * @return void + * @throws Exception + */ + public function addRemoveUserOrGroupToOrFromMount( + string $action, + string $userOrGroup, + string $userOrGroupName, + string $mountName + ):void { + if ($action === "adds" || $action === "added") { + $action = "--add"; + } else { + $action = "--remove"; + } + if ($userOrGroup === "user") { + $action = "$action-user"; + $userOrGroupName = $this->featureContext->getActualUsername($userOrGroupName); + } else { + $action = "$action-group"; + } + $mountId = $this->featureContext->getStorageId($mountName); + $this->featureContext->setOccLastCode( + $this->featureContext->runOcc( + [ + 'files_external:applicable', + $mountId, + "$action ", + "$userOrGroupName" + ] + ) + ); + } + + /** + * @param string $mount + * @param string $userOrGroup + * + * @return array + * @throws Exception + */ + public function getListOfApplicableUserOrGroupForMount(string $mount, string $userOrGroup):array { + $validArgs = ["users", "groups"]; + if (!\in_array($userOrGroup, $validArgs)) { + throw new Exception( + "Invalid key provided. Expected:" . + \implode(", ", $validArgs) . + "Found: " . $userOrGroup + ); + } + $mountId = $this->getMountIdForLocalStorage($mount); + $this->featureContext->runOcc( + [ + 'files_external:applicable', + $mountId, + '--output=json' + ] + ); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + return ($userOrGroup === "users") ? $commandOutput->users : $commandOutput->groups; + } + + /** + * @param string $action + * @param string $userOrGroup + * @param string $userOrGroupName + * + * @return void + * @throws Exception + */ + public function addRemoveUserOrGroupToOrFromLastLocalMount( + string $action, + string $userOrGroup, + string $userOrGroupName + ):void { + $storageIds = $this->featureContext->getStorageIds(); + Assert::assertGreaterThan( + 0, + \count($storageIds), + "addRemoveAsApplicableUserLastLocalMount no local mounts exist" + ); + $lastMountName = \end($storageIds); + $this->addRemoveUserOrGroupToOrFromMount( + $action, + $userOrGroup, + $userOrGroupName, + $lastMountName + ); + } + + /** + * @When /^the administrator (adds|removes) (user|group) "([^"]*)" (?:as|from) the applicable (?:user|group) for the last local storage mount using the occ command$/ + * + * @param string $action + * @param string $userOrGroup + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdminAddsRemovesAsTheApplicableUserLastLocalMountUsingTheOccCommand( + string $action, + string $userOrGroup, + string $user + ):void { + $this->addRemoveUserOrGroupToOrFromLastLocalMount( + $action, + $userOrGroup, + $user + ); + } + + /** + * @Given /^the administrator has (added|removed) (user|group) "([^"]*)" (?:as|from) the applicable (?:user|group) for the last local storage mount$/ + * + * @param string $action + * @param string $userOrGroup + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdminHasAddedRemovedAsTheApplicableUserLastLocalMountUsingTheOccCommand( + string $action, + string $userOrGroup, + string $user + ):void { + $this->addRemoveUserOrGroupToOrFromLastLocalMount( + $action, + $userOrGroup, + $user + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When /^the administrator (adds|removes) (user|group) "([^"]*)" (?:as|from) the applicable (?:user|group) for local storage mount "([^"]*)" using the occ command$/ + * + * @param string $action + * @param string $userOrGroup + * @param string $user + * @param string $mount + * + * @return void + * @throws Exception + */ + public function theAdminAddsRemovesAsTheApplicableUserForMountUsingTheOccCommand( + string $action, + string $userOrGroup, + string $user, + string $mount + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->addRemoveUserOrGroupToOrFromMount( + $action, + $userOrGroup, + $user, + $mount + ); + } + + /** + * @Then /^the following (users|groups) should be listed as applicable for local storage mount "([^"]*)"$/ + * + * @param string $usersOrGroups comma separated lists eg: Alice, Brian + * @param string $localStorage + * @param TableNode $applicable + * + * @return void + * @throws Exception + */ + public function theFollowingUsersOrGroupsShouldBeListedAsApplicable(string $usersOrGroups, string $localStorage, TableNode $applicable): void { + $this->featureContext->verifyTableNodeRows( + $applicable, + [], + ["users", "groups"] + ); + $expectedApplicableList = $applicable->getRowsHash(); + $actualApplicableList = $this->getListOfApplicableUserOrGroupForMount($localStorage, $usersOrGroups); + foreach ($expectedApplicableList as $expectedApplicable) { + Assert::assertContains( + $expectedApplicable, + $actualApplicableList, + __METHOD__ + . $usersOrGroups + . " not found!\nexpected: " + . $expectedApplicable + . " to be in the list [" + . \implode(", ", $actualApplicableList) + . "]." + ); + } + } + + /** + * @Then /^the applicable (users|groups) list should be empty for local storage mount "([^"]*)"$/ + * + * @param string $usersOrGroups + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function theApplicableUsersOrGroupsListShouldBeEmptyForLocalStorageMount(string $usersOrGroups, string $localStorage): void { + $actualApplicableList = $this->getListOfApplicableUserOrGroupForMount($localStorage, $usersOrGroups); + Assert::assertEquals( + 0, + \count($actualApplicableList), + __METHOD__ + . "Expected empty list for applicable " + . $usersOrGroups + . " but found: [" + . \implode(", ", $actualApplicableList) + . "]." + ); + } + + /** + * @When the administrator removes all from the applicable users and groups for local storage mount :localStorage using the occ command + * + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function theAdminRemovesAllForTheMountUsingOccCommand(string $localStorage):void { + $mountId = $this->featureContext->getStorageId($localStorage); + $this->featureContext->runOcc( + [ + 'files_external:applicable', + $mountId, + "--remove-all" + ] + ); + } + + /** + * @Given /^the administrator has (added|removed) (user|group) "([^"]*)" (?:as|from) the applicable (?:user|group) for local storage mount "([^"]*)"$/ + * + * @param string $action + * @param string $userOrGroup + * @param string $user + * @param string $mount + * + * @return void + * @throws Exception + */ + public function theAdminHasAddedRemovedTheApplicableUserForMountUsingTheOccCommand( + string $action, + string $userOrGroup, + string $user, + string $mount + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->addRemoveUserOrGroupToOrFromMount( + $action, + $userOrGroup, + $user, + $mount + ); + $this->theCommandShouldHaveBeenSuccessful(); + // making plural "users" or "groups" + $userOrGroup = $userOrGroup . "s"; + $actualApplicableList = $this->getListOfApplicableUserOrGroupForMount($mount, $userOrGroup); + + if ($action === "added") { + Assert::assertContains( + $user, + $actualApplicableList, + __METHOD__ + . " The expected applicable " + . $userOrGroup + . " is not present in the actual list of applicable " + . $userOrGroup + . " for mount point: " + . $mount . ".\n" + ); + } else { + Assert::assertNotContains( + $user, + $actualApplicableList, + __METHOD__ + . " The applicable " + . $userOrGroup + . " is present in the actual list of applicable " + . $userOrGroup + . " for mount point: " + . $mount . ".\n" + ); + } + } + + /** + * @When the administrator configures the key :key with value :value for the local storage mount :localStorage + * + * @param string $key + * @param string $value + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function adminConfiguresLocalStorageMountUsingTheOccCommand(string $key, string $value, string $localStorage):void { + $mountId = $this->featureContext->getStorageId($localStorage); + $this->featureContext->runOcc( + [ + "files_external:config", + $mountId, + $key, + $value + ] + ); + } + + /** + * @When the administrator lists the available backends using the occ command + * + * @return void + * @throws Exception + */ + public function adminListsAvailableBackendsUsingTheOccCommand():void { + $this->listAvailableBackends(); + } + + /** + * @When the administrator lists the available backends of type :type using the occ command + * + * @param String $type + * + * @return void + * @throws Exception + */ + public function adminListsAvailableBackendsOfTypeUsingTheOccCommand(string $type):void { + $this->listAvailableBackendsOfType($type); + } + + /** + * @When the administrator lists the :backend backend of type :backendType using the occ command + * + * @param String $backend + * @param String $backendType + * + * @return void + * @throws Exception + */ + public function adminListsBackendOfTypeUsingTheOccCommand(string $backend, string $backendType):void { + $this->listBackendOfType($backendType, $backend); + } + + /** + * @When the administrator lists configurations with the existing key :key for the local storage mount :localStorage + * + * @param string $key + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function adminListsConfigurationsWithExistingKeyForLocalStorageMountUsingTheOccCommand(string $key, string $localStorage):void { + $mountId = $this->featureContext->getStorageId($localStorage); + $this->featureContext->runOcc( + [ + "files_external:config", + $mountId, + $key, + ] + ); + } + + /** + * @Given the administrator has configured the key :key with value :value for the local storage mount :localStorage + * + * @param string $key + * @param string $value + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function adminConfiguredLocalStorageMountUsingTheOccCommand(string $key, string $value, string $localStorage):void { + $mountId = $this->featureContext->getStorageId($localStorage); + $this->featureContext->runOcc( + [ + "files_external:config", + $mountId, + $key, + $value + ] + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator lists the local storage using the occ command + * + * @return void + * @throws Exception + */ + public function adminListsLocalStorageMountUsingTheOccCommand():void { + $this->listLocalStorageMount(); + } + + /** + * @When the administrator lists the local storage with --short using the occ command + * + * @return void + * @throws Exception + */ + public function adminListsLocalStorageMountShortUsingTheOccCommand():void { + $this->listLocalStorageMountShort(); + } + + /** + * @When the administrator lists the local storage with --mount-options using the occ command + * + * @return void + * @throws Exception + */ + public function adminListsLocalStorageMountOptionsUsingTheOccCommand():void { + $this->listLocalStorageMountOptions(); + } + + /** + * @When the administrator lists the local storage with --show-password using the occ command + * + * @return void + * @throws Exception + */ + public function adminListsLocalStorageShowPasswordUsingTheOccCommand():void { + $this->listLocalStorageShowPassword(); + } + + /** + * @Then the following local storage should exist + * + * @param TableNode $mountPoints + * + * @return void + */ + public function theFollowingLocalStoragesShouldExist(TableNode $mountPoints):void { + $createdLocalStorage = []; + $expectedLocalStorages = $mountPoints->getColumnsHash(); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $storageEntry) { + $createdLocalStorage[$storageEntry->mount_id] = \ltrim($storageEntry->mount_point, '/'); + } + foreach ($expectedLocalStorages as $expectedStorageEntry) { + Assert::assertContains( + $expectedStorageEntry['localStorage'], + $createdLocalStorage, + "'" + . \implode(', ', $createdLocalStorage) + . "' does not contain '${expectedStorageEntry['localStorage']}' " + . __METHOD__ + ); + } + } + + /** + * @Then the following local storage should not exist + * + * @param TableNode $mountPoints + * + * @return void + * @throws Exception + */ + public function theFollowingLocalStoragesShouldNotExist(TableNode $mountPoints):void { + $createdLocalStorage = []; + $this->listLocalStorageMount(); + $expectedLocalStorages = $mountPoints->getColumnsHash(); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $i) { + $createdLocalStorage[$i->mount_id] = \ltrim($i->mount_point, '/'); + } + foreach ($expectedLocalStorages as $i) { + Assert::assertNotContains( + $i['localStorage'], + $createdLocalStorage, + "{$i['localStorage']} exists but was not expected to exist" + ); + } + } + + /** + * @Then the following backend types should be listed: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingBackendTypesShouldBeListed(TableNode $table):void { + $expectedBackendTypes = $table->getColumnsHash(); + foreach ($expectedBackendTypes as $expectedBackendTypeEntry) { + Assert::assertArrayHasKey( + 'backend-type', + $expectedBackendTypeEntry, + __METHOD__ + . " The provided expected backend type entry '" + . \implode(', ', $expectedBackendTypeEntry) + . "' do not have key 'backend-type'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $keys = \array_keys((array) $commandOutput); + foreach ($expectedBackendTypes as $backendTypesEntry) { + Assert::assertContains( + $backendTypesEntry['backend-type'], + $keys, + __METHOD__ + . " ${backendTypesEntry['backend-type']} is not contained in '" + . \implode(', ', $keys) + . "' but was expected to be." + ); + } + } + + /** + * @Then the following authentication/storage backends should be listed: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingBackendsShouldBeListed(TableNode $table):void { + $expectedBackends = $table->getColumnsHash(); + foreach ($expectedBackends as $expectedBackendEntry) { + Assert::assertArrayHasKey( + 'backends', + $expectedBackendEntry, + __METHOD__ + . " The provided expected backend entry '" + . \implode(', ', $expectedBackendEntry) + . "' do not have key 'backends'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $keys = \array_keys((array) $commandOutput); + foreach ($expectedBackends as $backendsEntry) { + Assert::assertContains( + $backendsEntry['backends'], + $keys, + __METHOD__ + . " ${backendsEntry['backends']} is not contained in '" + . \implode(', ', $keys) + . "' but was expected to be." + ); + } + } + + /** + * @Then the following authentication/storage backend keys should be listed: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingBackendKeysOfTypeShouldBeListed(TableNode $table):void { + $expectedBackendKeys = $table->getColumnsHash(); + foreach ($expectedBackendKeys as $expectedBackendKeyEntry) { + Assert::assertArrayHasKey( + 'backend-keys', + $expectedBackendKeyEntry, + __METHOD__ + . " The provided expected backend key entry '" + . \implode(', ', $expectedBackendKeyEntry) + . "' do not have key 'backend-keys'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $keys = \array_keys((array) $commandOutput); + foreach ($expectedBackendKeys as $backendKeysEntry) { + Assert::assertContains( + $backendKeysEntry['backend-keys'], + $keys, + __METHOD__ + . " ${backendKeysEntry['backend-keys']} is not contained in '" + . \implode(', ', $keys) + . "' but was expected to be." + ); + } + } + + /** + * @Then the following local storage should be listed: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingLocalStorageShouldBeListed(TableNode $table):void { + $this->featureContext->verifyTableNodeColumns( + $table, + ['MountPoint', 'ApplicableUsers', 'ApplicableGroups'], + ['Storage', 'AuthenticationType', 'Configuration', 'Options', 'Auth', 'Type'] + ); + $expectedLocalStorages = $table->getColumnsHash(); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($expectedLocalStorages as $expectedStorageEntry) { + $isStorageEntryListed = false; + foreach ($commandOutput as $listedStorageEntry) { + if ($expectedStorageEntry["MountPoint"] === $listedStorageEntry->mount_point) { + if (isset($expectedStorageEntry['Storage'])) { + Assert::assertEquals( + $expectedStorageEntry['Storage'], + $listedStorageEntry->storage, + "Storage column does not have the expected value for storage " + . $expectedStorageEntry['MountPoint'] + ); + } + if (isset($expectedStorageEntry['AuthenticationType'])) { + Assert::assertEquals( + $expectedStorageEntry['AuthenticationType'], + $listedStorageEntry->authentication_type, + "AuthenticationType column does not have the expected value for storage " + . $expectedStorageEntry['MountPoint'] + ); + } + if (isset($expectedStorageEntry['Auth'])) { + Assert::assertEquals( + $expectedStorageEntry['Auth'], + $listedStorageEntry->auth, + "Auth column does not have the expected value for storage " + . $expectedStorageEntry['MountPoint'] + ); + } + if (isset($expectedStorageEntry['Configuration'])) { + if ($expectedStorageEntry['Configuration'] === '') { + Assert::assertEquals( + '', + $listedStorageEntry->configuration, + 'Configuration column should be empty but is ' + . $listedStorageEntry->configuration + ); + } else { + if (\is_string($listedStorageEntry->configuration)) { + Assert::assertStringStartsWith( + $expectedStorageEntry['Configuration'], + $listedStorageEntry->configuration, + "Configuration column does not start with the expected value for storage " + . $expectedStorageEntry['Configuration'] + ); + } else { + $item = \strtok($expectedStorageEntry['Configuration'], ':'); + Assert::assertTrue( + \property_exists($listedStorageEntry->configuration, $item), + "$item was not found in the Configuration column" + ); + } + } + } + if (isset($expectedStorageEntry['Options'])) { + Assert::assertEquals( + $expectedStorageEntry['Options'], + $listedStorageEntry->options, + "Options column does not have the expected value for storage " + . $expectedStorageEntry['MountPoint'] + ); + } + if (isset($expectedStorageEntry['ApplicableUsers'])) { + if (\is_string($listedStorageEntry->applicable_users)) { + if ($listedStorageEntry->applicable_users === '') { + $listedApplicableUsers = []; + } else { + $listedApplicableUsers = \explode(', ', $listedStorageEntry->applicable_users); + } + } else { + $listedApplicableUsers = $listedStorageEntry->applicable_users; + } + if ($expectedStorageEntry['ApplicableUsers'] === '') { + Assert::assertEquals( + [], + $listedApplicableUsers, + "ApplicableUsers was expected to be an empty array but was not empty" + ); + } else { + $expectedApplicableUsers = \explode(', ', $expectedStorageEntry['ApplicableUsers']); + foreach ($expectedApplicableUsers as $expectedApplicableUserEntry) { + $expectedApplicableUserEntry = $this->featureContext->getActualUsername($expectedApplicableUserEntry); + Assert::assertContains( + $expectedApplicableUserEntry, + $listedApplicableUsers, + __METHOD__ + . " '$expectedApplicableUserEntry' is not listed in '" + . \implode(', ', $listedApplicableUsers) + . "'" + ); + } + } + } + if (isset($expectedStorageEntry['ApplicableGroups'])) { + if (\is_string($listedStorageEntry->applicable_groups)) { + if ($listedStorageEntry->applicable_groups === '') { + $listedApplicableGroups = []; + } else { + $listedApplicableGroups = \explode(', ', $listedStorageEntry->applicable_groups); + } + } else { + $listedApplicableGroups = $listedStorageEntry->applicable_groups; + } + if ($expectedStorageEntry['ApplicableGroups'] === '') { + Assert::assertEquals( + [], + $listedApplicableGroups, + "ApplicableGroups was expected to be an empty array but was not empty" + ); + Assert::assertEquals([], $listedApplicableGroups); + } else { + $expectedApplicableGroups = \explode(', ', $expectedStorageEntry['ApplicableGroups']); + foreach ($expectedApplicableGroups as $expectedApplicableGroupEntry) { + Assert::assertContains( + $expectedApplicableGroupEntry, + $listedApplicableGroups, + __METHOD__ + . " '$expectedApplicableGroupEntry' is not listed in '" + . \implode(', ', $listedApplicableGroups) + . "'" + ); + } + } + } + if (isset($expectedStorageEntry['Type'])) { + Assert::assertEquals( + $expectedStorageEntry['Type'], + $listedStorageEntry->type, + "Type column does not have the expected value for storage " + . $expectedStorageEntry['MountPoint'] + ); + } + $isStorageEntryListed = true; + break; + } + } + Assert::assertTrue($isStorageEntryListed, __METHOD__ . " Expected local storage {$expectedStorageEntry['MountPoint']} not found"); + } + } + + /** + * @Then the configuration output should be :expectedOutput + * + * @param string $expectedOutput + * + * @return void + * @throws Exception + */ + public function theConfigurationOutputShouldBe(string $expectedOutput):void { + $actualOutput = $this->featureContext->getStdOutOfOccCommand(); + $trimmedOutput = \trim($actualOutput); + Assert::assertEquals( + $expectedOutput, + $trimmedOutput, + __METHOD__ + . " The expected configuration output was '$expectedOutput', but got '$actualOutput' instead." + ); + } + + /** + * @Then the following should be included in the configuration of local storage :localStorage: + * + * @param string $localStorage + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingShouldBeIncludedInTheConfigurationOfLocalStorage(string $localStorage, TableNode $table):void { + $expectedConfigurations = $table->getColumnsHash(); + foreach ($expectedConfigurations as $expectedConfigurationEntry) { + Assert::assertArrayHasKey( + 'configuration', + $expectedConfigurationEntry, + __METHOD__ + . " The provided expected configuration entry '" + . \implode(', ', $expectedConfigurationEntry) + . "' do not have key 'configuration'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $isStorageEntryListed = false; + foreach ($commandOutput as $listedStorageEntry) { + if ($listedStorageEntry->mount_point === $localStorage) { + $isStorageEntryListed = true; + $configurations = $listedStorageEntry->configuration; + $configurationsSplitted = \explode(', ', $configurations); + foreach ($expectedConfigurations as $expectedConfigArray) { + foreach ($expectedConfigArray as $expectedConfigEntry) { + Assert::assertContains( + $expectedConfigEntry, + $configurationsSplitted, + __METHOD__ + . " $expectedConfigEntry is not contained in '" + . \implode(', ', $configurationsSplitted) + . "' but was expected to be." + ); + } + } + break; + } + } + Assert::assertTrue($isStorageEntryListed, "Expected local storage '$localStorage' not found "); + } + + /** + * @When the administrator adds an option with key :key and value :value for the local storage mount :localStorage + * + * @param string $key + * @param string $value + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function adminAddsOptionForLocalStorageMountUsingTheOccCommand(string $key, string $value, string $localStorage):void { + $mountId = $this->featureContext->getStorageId($localStorage); + $this->featureContext->runOcc( + [ + "files_external:option", + $mountId, + $key, + $value + ] + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @Then the following should be included in the options of local storage :localStorage: + * + * @param string $localStorage + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingShouldBeIncludedInTheOptionsOfLocalStorage(string $localStorage, TableNode $table):void { + $expectedOptions = $table->getColumnsHash(); + foreach ($expectedOptions as $expectedOptionEntry) { + Assert::assertArrayHasKey( + 'options', + $expectedOptionEntry, + __METHOD__ + . " The provided expected option '" + . \implode(', ', $expectedOptionEntry) + . "' do not have key 'option'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $isStorageEntryListed = false; + foreach ($commandOutput as $listedStorageEntry) { + if ($listedStorageEntry->mount_point === $localStorage) { + $isStorageEntryListed = true; + $options = $listedStorageEntry->options; + $optionsSplitted = \explode(', ', $options); + foreach ($expectedOptions as $expectedOptionArray) { + foreach ($expectedOptionArray as $expectedOptionEntry) { + Assert::assertContains( + $expectedOptionEntry, + $optionsSplitted, + __METHOD__ + . " $expectedOptionEntry is not contained in '" + . \implode(', ', $optionsSplitted) + . "' , but was expected to be." + ); + } + } + break; + } + } + Assert::assertTrue($isStorageEntryListed, "Expected local storage '$localStorage' not found "); + } + + /** + * @Then the following should not be included in the options of local storage :localStorage: + * + * @param string $localStorage + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingShouldNotBeIncludedInTheOptionsOfLocalStorage(string $localStorage, TableNode $table):void { + $expectedOptions = $table->getColumnsHash(); + foreach ($expectedOptions as $expectedOptionEntry) { + Assert::assertArrayHasKey( + 'options', + $expectedOptionEntry, + __METHOD__ + . " The provided expected option '" + . \implode(', ', $expectedOptionEntry) + . "' do not have key 'options'" + ); + } + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + $isStorageEntryListed = false; + foreach ($commandOutput as $listedStorageEntry) { + if ($listedStorageEntry->mount_point === $localStorage) { + $isStorageEntryListed = true; + $options = $listedStorageEntry->options; + $optionsSplitted = \explode(', ', $options); + foreach ($expectedOptions as $expectedOptionArray) { + foreach ($expectedOptionArray as $expectedOptionEntry) { + Assert::assertNotContains( + $expectedOptionEntry, + $optionsSplitted, + __METHOD__ + . " $expectedOptionEntry is contained in '" + . \implode(', ', $optionsSplitted) + . "' , but was not expected to be." + ); + } + } + break; + } + } + Assert::assertTrue($isStorageEntryListed, "Expected local storage '$localStorage' not found "); + } + + /** + * @When the administrator deletes local storage :folder using the occ command + * + * @param string $folder + * + * @return integer|boolean + * @throws Exception + */ + public function administratorDeletesFolder(string $folder) { + return $this->deleteLocalStorageFolderUsingTheOccCommand($folder); + } + + /** + * @Given the administrator has deleted local storage :folder using the occ command + * + * @param string $folder + * + * @return integer + * @throws Exception + */ + public function administratorHasDeletedLocalStorageFolderUsingTheOccCommand(string $folder):int { + $mount_id = $this->deleteLocalStorageFolderUsingTheOccCommand($folder); + $this->theCommandShouldHaveBeenSuccessful(); + return $mount_id; + } + + /** + * @param string $folder + * + * @return integer|null + * @throws Exception + */ + public function getMountIdForLocalStorage(string $folder): ?int { + $createdLocalStorage = []; + $mount_id = null; + $this->listLocalStorageMount(); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $i) { + $createdLocalStorage[$i->mount_id] = \ltrim($i->mount_point, '/'); + } + foreach ($createdLocalStorage as $key => $value) { + if ($value === $folder) { + $mount_id = $key; + } + } + + return (int) $mount_id; + } + + /** + * @param string $folder + * @param bool $mustExist + * + * @return integer|bool + * @throws Exception + */ + public function deleteLocalStorageFolderUsingTheOccCommand(string $folder, bool $mustExist = true) { + $mount_id = $this->getMountIdForLocalStorage($folder); + + if (!isset($mount_id)) { + if ($mustExist) { + throw new Exception("Id not found for folder to be deleted"); + } + return false; + } + $this->invokingTheCommand('files_external:delete --yes ' . $mount_id); + return $mount_id; + } + + /** + * @When the administrator exports the local storage mounts using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorExportsTheMountsUsingTheOccCommand():void { + $this->invokingTheCommand('files_external:export'); + } + + /** + * @When the administrator imports the local storage mount from file :file using the occ command + * + * @param string $file + * + * @return void + * @throws Exception + */ + public function theAdministratorImportsTheMountFromFileUsingTheOccCommand(string $file):void { + $this->invokingTheCommand( + 'files_external:import ' . TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/$file" + ); + } + + /** + * @Given /^the administrator has exported the (local|external) storage mounts using the occ command$/ + * + * @return void + * @throws Exception + */ + public function theAdministratorHasExportedTheMountsUsingTheOccCommand():void { + $this->invokingTheCommand('files_external:export'); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @Then the command should output configuration for local storage mount :mount + * + * @param string $mount + * + * @return void + * @throws Exception + */ + public function theOutputShouldContainConfigurationForMount(string $mount):void { + $actualConfig = null; + + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $i) { + if ($mount === \ltrim($i->mount_point, '/')) { + $actualConfig = $i; + break; + } + } + + Assert::assertNotNull($actualConfig, 'Configuration for local storage mount ' . $mount . ' not found.'); + } + + /** + * @When the administrator verifies the mount configuration for local storage :localStorage using the occ command + * + * @param string $localStorage + * + * @return void + * @throws Exception + */ + public function theAdministratorVerifiesTheMountConfigurationForLocalStorageUsingTheOccCommand(string $localStorage):void { + $this->listLocalStorageMount(); + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $entry) { + if (\ltrim($entry->mount_point, '/') == $localStorage) { + $mountId = $entry->mount_id; + } + } + if (!isset($mountId)) { + throw new Exception("Id not found for local storage $localStorage to be verified"); + } + $this->invokingTheCommand('files_external:verify ' . $mountId); + } + + /** + * @Then the following mount configuration information should be listed: + * + * @param TableNode $info + * + * @return void + */ + public function theFollowingInformationShouldBeListed(TableNode $info):void { + $ResultArray = []; + $expectedInfo = $info->getColumnsHash(); + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $commandOutputSplitted = \preg_split("/[-]/", $commandOutput); + $filteredArray = \array_filter(\array_map("trim", $commandOutputSplitted)); + foreach ($filteredArray as $entry) { + $keyValue = \preg_split("/[:]/", $entry); + if (isset($keyValue[1])) { + $ResultArray[$keyValue[0]] = $keyValue[1]; + } else { + $ResultArray[$keyValue[0]] = ""; + } + } + foreach ($expectedInfo as $element) { + Assert::assertEquals( + $element, + \array_map('trim', $ResultArray), + __METHOD__ + . " '" . \implode(', ', $element) + . "' was expected to be listed, but is not listed in the mount configuration information" + ); + } + } + + /** + * @When the administrator list the repair steps using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorListTheRepairStepsUsingTheOccCommand():void { + $this->invokingTheCommand('maintenance:repair --list'); + } + + /** + * @Then the background jobs mode should be :mode + * + * @param string $mode + * + * @return void + * @throws Exception + */ + public function theBackgroundJobsModeShouldBe(string $mode):void { + $this->invokingTheCommand( + "config:app:get core backgroundjobs_mode" + ); + $lastOutput = $this->featureContext->getStdOutOfOccCommand(); + Assert::assertEquals( + $mode, + \trim($lastOutput), + "The background jobs mode was expected to be {$mode} but got '" + . \trim($lastOutput) + . "'" + ); + } + + /** + * @Then the update channel should be :value + * + * @param string $value + * + * @return void + * @throws Exception + */ + public function theUpdateChannelShouldBe(string $value):void { + $this->invokingTheCommand( + "config:app:get core OC_Channel" + ); + $lastOutput = $this->featureContext->getStdOutOfOccCommand(); + Assert::assertEquals( + $value, + \trim($lastOutput), + "The update channel was expected to be '$value' but got '" + . \trim($lastOutput) + . "'" + ); + } + + /** + * @Then the log level should be :logLevel + * + * @param string $logLevel + * + * @return void + * @throws Exception + */ + public function theLogLevelShouldBe(string $logLevel):void { + $this->invokingTheCommand( + "config:system:get loglevel" + ); + $lastOutput = $this->featureContext->getStdOutOfOccCommand(); + Assert::assertEquals( + $logLevel, + \trim($lastOutput), + "The log level was expected to be '$logLevel' but got '" + . \trim($lastOutput) + . "'" + ); + } + + /** + * @When the administrator adds/updates config key :key with value :value in app :app using the occ command + * + * @param string $key + * @param string $value + * @param string $app + * + * @return void + * @throws Exception + */ + public function theAdministratorAddsConfigKeyWithValueInAppUsingTheOccCommand(string $key, string $value, string $app):void { + $this->addConfigKeyWithValueInAppUsingTheOccCommand( + $key, + $value, + $app + ); + } + + /** + * @Given the administrator has added config key :key with value :value in app :app + * + * @param string $key + * @param string $value + * @param string $app + * + * @return void + * @throws Exception + */ + public function theAdministratorHasAddedConfigKeyWithValueInAppUsingTheOccCommand(string $key, string $value, string $app):void { + $this->addConfigKeyWithValueInAppUsingTheOccCommand( + $key, + $value, + $app + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator deletes config key :key of app :app using the occ command + * + * @param string $key + * @param string $app + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesConfigKeyOfAppUsingTheOccCommand(string $key, string $app):void { + $this->deleteConfigKeyOfAppUsingTheOccCommand($key, $app); + } + + /** + * @When the administrator adds/updates system config key :key with value :value using the occ command + * @When the administrator adds/updates system config key :key with value :value and type :type using the occ command + * + * @param string $key + * @param string $value + * @param string $type + * + * @return void + * @throws Exception + */ + public function theAdministratorAddsSystemConfigKeyWithValueUsingTheOccCommand( + string $key, + string $value, + string $type = "string" + ):void { + $this->addSystemConfigKeyUsingTheOccCommand( + $key, + $value, + $type + ); + } + + /** + * @Given the administrator has added/updated system config key :key with value :value + * @Given the administrator has added/updated system config key :key with value :value and type :type + * + * @param string $key + * @param string $value + * @param string $type + * + * @return void + * @throws Exception + */ + public function theAdministratorHasAddedSystemConfigKeyWithValueUsingTheOccCommand( + string $key, + string $value, + string $type = "string" + ):void { + $this->addSystemConfigKeyUsingTheOccCommand( + $key, + $value, + $type + ); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator deletes system config key :key using the occ command + * + * @param string $key + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesSystemConfigKeyUsingTheOccCommand(string $key):void { + $this->deleteSystemConfigKeyUsingTheOccCommand($key); + } + + /** + * @When the administrator empties the trashbin of user :user using the occ command + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorEmptiesTheTrashbinOfUserUsingTheOccCommand(string $user):void { + $this->emptyTrashBinOfUserUsingOccCommand($user); + } + + /** + * @When the administrator deletes all the versions for user :user + * @When the administrator tries to delete all the versions for user :user + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesAllTheVersionsForUser(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $this->deleteAllVersionsForUserUsingOccCommand($user); + } + + /** + * @When the administrator cleanups all the orphaned remote storages of shares using the occ command + * + * @return void + * @throws Exception + */ + public function theAdminCleanupsOrphanedRemoteStoragesOfSharesUsingOccCommand():void { + $this->cleanOrphanedRemoteStoragesUsingOccCommand(); + } + + /** + * @When the administrator deletes all the versions for the following users: + * + * @param TableNode $usersTable + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesAllTheVersionsForSpecificUsers(TableNode $usersTable):void { + $this->featureContext->verifyTableNodeColumns($usersTable, ["username"]); + $usernames = $usersTable->getHash(); + $usernamesArray = []; + foreach ($usernames as $username) { + array_push($usernamesArray, $username["username"]); + } + $users = implode(" ", $usernamesArray); + $this->deleteAllVersionsForMultipleUsersUsingOccCommand($users); + } + + /** + * @When the administrator deletes the file versions for all users + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesVersionsForAllUsers(): void { + $this->deleteAllVersionsForAllUsersUsingOccCommand(); + } + + /** + * @When the administrator deletes the expired versions for user :user + * @When the administrator tries to delete the expired versions for user :user + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesExpiredVersionsForUser(string $user): void { + $user = $this->featureContext->getActualUsername($user); + $this->deleteExpiredVersionsForUserUsingOccCommand($user); + } + + /** + * @When the administrator deletes the expired versions for the following users: + * + * @param TableNode $usersTable + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesExpiredVersionsForSpecificUsers(TableNode $usersTable): void { + $this->featureContext->verifyTableNodeColumns($usersTable, ["username"]); + $usernames = $usersTable->getHash(); + $usernamesArray = []; + foreach ($usernames as $username) { + array_push($usernamesArray, $username["username"]); + } + $users = implode(" ", $usernamesArray); + $this->deleteExpiredVersionsForMultipleUsersUsingOccCommand($users); + } + + /** + * @When the administrator deletes the expired versions for all users + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesExpiredVersionsForAllUsers(): void { + $this->deleteExpiredVersionsForAllUsersUsingOccCommand(); + } + + /** + * @When the administrator empties the trashbin of all users using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorEmptiesTheTrashbinOfAllUsersUsingTheOccCommand():void { + $this->emptyTrashBinOfUserUsingOccCommand(''); + } + + /** + * @When the administrator creates a calendar for user :user with name :calendarName using the occ command + * + * @param string $user + * @param string $calendarName + * + * @return void + * @throws Exception + */ + public function theAdminCreatesACalendarForUserUsingTheOccCommand(string $user, string $calendarName):void { + $user = $this->featureContext->getActualUsername($user); + $this->createCalendarForUserUsingOccCommand($user, $calendarName); + } + + /** + * @When the administrator creates an address book for user :user with name :addressBookName using the occ command + * + * @param string $user + * @param string $addressBookName + * + * @return void + * @throws Exception + */ + public function theAdminCreatesAnAddressBookForUserUsingTheOccCommand(string $user, string $addressBookName):void { + $user = $this->featureContext->getActualUsername($user); + $this->createAnAddressBookForUserUsingOccCommand($user, $addressBookName); + } + + /** + * @When the administrator gets all the jobs in the background queue using the occ command + * + * @return void + * @throws Exception + */ + public function theAdministratorGetsAllTheJobsInTheBackgroundQueueUsingTheOccCommand():void { + $this->getAllJobsInBackgroundQueueUsingOccCommand(); + } + + /** + * @When the administrator deletes the last background job :job using the occ command + * + * @param string $job + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesLastBackgroundJobUsingTheOccCommand(string $job):void { + $this->deleteLastBackgroundJobUsingTheOccCommand($job); + } + + /** + * @Then the last deleted background job :job should not be listed in the background jobs queue + * + * @param string $job + * + * @return void + * @throws Exception + */ + public function theLastDeletedJobShouldNotBeListedInTheJobsQueue(string $job):void { + $jobId = $this->lastDeletedJobId; + $match = $this->getLastJobIdForJob($job); + if ($match) { + Assert::assertNotEquals( + $jobId, + $match, + "job $job with jobId $jobId" . + " was not expected to be listed in background queue, but was" + ); + } + } + + /** + * @Then system config key :key should have value :value + * + * @param string $key + * @param string $value + * + * @return void + * @throws Exception + */ + public function systemConfigKeyShouldHaveValue(string $key, string $value):void { + $config = \trim( + SetupHelper::getSystemConfigValue( + $key, + $this->featureContext->getStepLineRef() + ) + ); + Assert::assertSame( + $value, + $config, + "The system config key '$key' was expected to have value '$value', but got '$config'" + ); + } + + /** + * @Then the command output table should contain the following text: + * + * @param TableNode $table table of patterns to find with table title as 'table_column' + * + * @return void + * @throws Exception + */ + public function theCommandOutputTableShouldContainTheFollowingText(TableNode $table):void { + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $this->featureContext->verifyTableNodeColumns($table, ['table_column']); + foreach ($table as $row) { + $lines = SetupHelper::findLines( + $commandOutput, + $row['table_column'] + ); + Assert::assertNotEmpty( + $lines, + "Value: " . $row['table_column'] . " not found" + ); + } + } + + /** + * @Then system config key :key should not exist + * + * @param string $key + * + * @return void + * @throws Exception + */ + public function systemConfigKeyShouldNotExist(string $key):void { + Assert::assertEmpty( + SetupHelper::getSystemConfig( + $key, + $this->featureContext->getStepLineRef() + )['stdOut'], + "The system config key '$key' was not expected to exist" + ); + } + + /** + * @When the administrator lists the config keys + * + * @return void + * @throws Exception + */ + public function theAdministratorListsTheConfigKeys():void { + $this->invokingTheCommand( + "config:list" + ); + } + + /** + * @Then the command output should contain the apps configs + * + * @return void + */ + public function theCommandOutputShouldContainTheAppsConfigs():void { + $config_list = \json_decode($this->featureContext->getStdOutOfOccCommand(), true); + Assert::assertArrayHasKey( + 'apps', + $config_list, + "The occ output does not contain apps configs" + ); + Assert::assertNotEmpty( + $config_list['apps'], + "The occ output does not contain apps configs" + ); + } + + /** + * @Then the command output should contain the system configs + * + * @return void + */ + public function theCommandOutputShouldContainTheSystemConfigs():void { + $config_list = \json_decode($this->featureContext->getStdOutOfOccCommand(), true); + Assert::assertArrayHasKey( + 'system', + $config_list, + "The occ output does not contain system configs" + ); + Assert::assertNotEmpty( + $config_list['system'], + "The occ output does not contain system configs" + ); + } + + /** + * @Given the administrator has cleared the versions for user :user + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorHasClearedTheVersionsForUser(string $user):void { + $user = $this->featureContext->getActualUsername($user); + $this->deleteAllVersionsForUserUsingOccCommand($user); + Assert::assertSame( + "Delete versions of $user", + \trim($this->featureContext->getStdOutOfOccCommand()) + ); + } + + /** + * @Given the administrator has cleared the versions for all users + * + * @return void + * @throws Exception + */ + public function theAdministratorHasClearedTheVersionsForAllUsers():void { + $this->deleteAllVersionsForAllUsersUsingOccCommand(); + Assert::assertStringContainsString( + "Delete all versions", + \trim($this->featureContext->getStdOutOfOccCommand()), + "Expected 'Delete all versions' to be contained in the output of occ command: " + . \trim($this->featureContext->getStdOutOfOccCommand()) + ); + } + + /** + * get jobId of the latest job found of given job class + * + * @param string $job + * + * @return string|bool + * @throws Exception + */ + public function getLastJobIdForJob(string $job) { + $this->getAllJobsInBackgroundQueueUsingOccCommand(); + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $lines = SetupHelper::findLines( + $commandOutput, + $job + ); + if (!$lines) { + return false; + } + // find the jobId of the newest job among the jobs with given class + $success = \preg_match("/\d+/", \end($lines), $match); + if ($success) { + return $match[0]; + } + return false; + } + + /** + * @Then the system config key :key from the last command output should match value :value of type :type + * + * @param string $key + * @param string $value + * @param string $type + * + * @return void + */ + public function theSystemConfigKeyFromLastCommandOutputShouldContainValue( + string $key, + string $value, + string $type + ):void { + $configList = \json_decode( + $this->featureContext->getStdOutOfOccCommand(), + true + ); + $systemConfig = $configList['system']; + + // convert the value to it's respective type based on type given in the type column + if ($type === 'boolean') { + $value = $value === 'true' ? true : false; + } elseif ($type === 'integer') { + $value = (int) $value; + } elseif ($type === 'json') { + // if the expected value of the key is a json + // match the value with the regular expression + $actualKeyValuePair = \json_encode( + $systemConfig[$key], + JSON_UNESCAPED_SLASHES + ); + + Assert::assertThat( + $actualKeyValuePair, + Assert::matchesRegularExpression($value) + ); + return; + } + + if (!\array_key_exists($key, $systemConfig)) { + Assert::fail( + "system config doesn't contain key: " . $key + ); + } + + Assert::assertEquals( + $value, + $systemConfig[$key], + "config: $key doesn't contain value: $value" + ); + } + + /** + * @Given the administrator has enabled the external storage + * + * @return void + * @throws Exception + */ + public function enableExternalStorageUsingOccAsAdmin():void { + SetupHelper::runOcc( + [ + 'config:app:set', + 'core', + 'enable_external_storage', + '--value=yes' + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $response = SetupHelper::runOcc( + [ + 'config:app:get', + 'core', + 'enable_external_storage', + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $status = \trim($response['stdOut']); + Assert::assertEquals( + 'yes', + $status, + "The external storage was expected to be enabled but got '$status'" + ); + } + + /** + * @Given the administrator has added group :group to the exclude group from sharing list + * + * @param string $groups + * multiple groups can be passed as comma separated string + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function theAdministratorHasAddedGroupToTheExcludeGroupFromSharingList(string $groups):void { + $groups = \explode(',', \trim($groups)); + $groups = \array_map('trim', $groups); //removing whitespaces around group names + $groups = '"' . \implode('","', $groups) . '"'; + SetupHelper::runOcc( + [ + 'config:app:set', + 'core', + 'shareapi_exclude_groups_list', + "--value='[$groups]'" + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $response = SetupHelper::runOcc( + [ + 'config:app:get', + 'core', + 'shareapi_exclude_groups_list' + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $excludedGroupsFromResponse = (\trim($response['stdOut'])); + $excludedGroupsFromResponse = \trim($excludedGroupsFromResponse, '[]'); + Assert::assertEquals( + $groups, + $excludedGroupsFromResponse, + "'$groups' is not added to exclude groups from sharing list: '" + . $excludedGroupsFromResponse + . "' but was expected to be" + ); + } + + /** + * @Given the administrator has enabled exclude groups from sharing + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEnabledExcludeGroupsFromSharingUsingTheWebui():void { + SetupHelper::runOcc( + [ + "config:app:set", + "core", + "shareapi_exclude_groups", + "--value=yes" + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $response = SetupHelper::runOcc( + [ + "config:app:get", + "core", + "shareapi_exclude_groups" + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $status = \trim($response['stdOut']); + Assert::assertEquals( + "yes", + $status, + "Exclude groups from sharing was expected to be 'yes'(enabled) but got '$status'" + ); + } + + /** + * @Given /^the administrator has (enabled|disabled) the webUI lock file action$/ + * + * @param string $enabledOrDisabled + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEnabledTheWebUILockFileAction(string $enabledOrDisabled):void { + $switch = ($enabledOrDisabled !== "disabled"); + if ($switch) { + $value = 'yes'; + } else { + $value = 'no'; + } + SetupHelper::runOcc( + [ + "config:app:set", + "files", + "enable_lock_file_action", + "--value=$value" + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $response = SetupHelper::runOcc( + [ + "config:app:get", + "files", + "enable_lock_file_action" + ], + $this->featureContext->getStepLineRef(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $status = \trim($response['stdOut']); + Assert::assertEquals( + $value, + $status, + "enable_lock_file_action was expected to be '$value'($enabledOrDisabled) but got '$status'" + ); + } + + /** + * @When the administrator creates an external mount point with the following configuration about user :user using the occ command + * + * @param string $user + * @param TableNode $settings + * + * necessary attributes inside $settings table: + * 1. host - remote server url + * 2. root - remote folder name -> mount path + * 3. secure - true/false (http or https) + * 4. user - remote server user username + * 5. password - remote server user password + * 6. mount_point - external storage name + * 7. storage_backend - options: [local, owncloud, smb, googledrive, sftp, dav] + * 8. authentication_backend - options: [null::null, password::password, password::sessioncredentials] + * + * @see [`php occ files_external:backends`] to view + * detailed information of parameters used above + * + * @return void + * @throws Exception + */ + public function createExternalMountPointUsingTheOccCommand(string $user, TableNode $settings):void { + $userRenamed = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeRows( + $settings, + ["host", "root", "storage_backend", + "authentication_backend", "mount_point", + "user", "password", "secure"] + ); + $extMntSettings = $settings->getRowsHash(); + $extMntSettings['user'] = $this->featureContext->substituteInLineCodes( + $extMntSettings['user'], + $userRenamed + ); + $password = $this->featureContext->substituteInLineCodes( + $extMntSettings['password'], + $user + ); + $args = [ + "files_external:create", + "-c host=" . + $this->featureContext->substituteInLineCodes($extMntSettings['host']), + "-c root=" . $extMntSettings['root'], + "-c secure=" . $extMntSettings['secure'], + "-c user=" . $extMntSettings['user'], + "-c password=" . $password, + $extMntSettings['mount_point'], + $extMntSettings['storage_backend'], + $extMntSettings['authentication_backend'] + ]; + $this->featureContext->setOccLastCode( + $this->featureContext->runOcc($args) + ); + // add to array of created storageIds + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $mountId = \preg_replace('/\D/', '', $commandOutput); + $this->featureContext->addStorageId($extMntSettings["mount_point"], (int) $mountId); + } + + /** + * @Given the administrator has created an external mount point with the following configuration about user :user using the occ command + * + * @param string $user + * @param TableNode $settings + * + * @return void + * @throws Exception + */ + public function adminHasCreatedAnExternalMountPointWithFollowingConfigUsingTheOccCommand(string $user, TableNode $settings):void { + $this->createExternalMountPointUsingTheOccCommand($user, $settings); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + private function deleteExternalMountPointUsingTheAdmin(string $mountPoint):void { + $mount_id = $this->administratorDeletesFolder($mountPoint); + $this->featureContext->popStorageId($mount_id); + } + + /** + * @Given the administrator has deleted external storage with mount point :mountPoint + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function adminHasDeletedExternalMountPoint(string $mountPoint):void { + $this->deleteExternalMountPointUsingTheAdmin($mountPoint); + $this->theCommandShouldHaveBeenSuccessful(); + } + + /** + * @When the administrator deletes external storage with mount point :mountPoint + * + * @param string $mountPoint + * + * @return void + * @throws Exception + */ + public function adminDeletesExternalMountPoint(string $mountPoint):void { + $this->deleteExternalMountPointUsingTheAdmin($mountPoint); + } + + /** + * @Then mount point :mountPoint should not be listed as an external storage + * + * @param string $mountPoint + * + * @return void + */ + public function mountPointShouldNotBeListedAsAnExternalStorage(string $mountPoint):void { + $commandOutput = \json_decode($this->featureContext->getStdOutOfOccCommand()); + foreach ($commandOutput as $entry) { + Assert::assertNotEquals($mountPoint, $entry->mount_point); + } + } + + /** + * @Given the administrator has changed the database type to :dbType + * + * @param string $dbType + * + * @return void + * @throws Exception + */ + public function theAdministratorHasChangedTheDatabaseType(string $dbType): void { + $this->theAdministratorChangesTheDatabaseType($dbType); + $exitStatusCode = $this->featureContext->getExitStatusCodeOfOccCommand(); + + if ($exitStatusCode !== 0) { + $exceptions = $this->featureContext->findExceptions(); + $commandErr = $this->featureContext->getStdErrOfOccCommand(); + $sameTypeError = "Can not convert from $dbType to $dbType."; + $lines = SetupHelper::findLines( + $commandErr, + $sameTypeError + ); + // pass if the same type error is found + if (\count($lines) === 0) { + $msg = "The command was not successful, exit code was " . + $exitStatusCode . ".\n" . + "stdOut was: '" . + $this->featureContext->getStdOutOfOccCommand() . "'\n" . + "stdErr was: '$commandErr'\n"; + if (!empty($exceptions)) { + $msg .= ' Exceptions: ' . \implode(', ', $exceptions); + } + throw new Exception($msg); + } + } + } + + /** + * @When the administrator changes the database type to :dbType + * @When the administrator tries to change the database type to :dbType + * + * @param string $dbType + * + * @return void + * @throws Exception + */ + public function theAdministratorChangesTheDatabaseType(string $dbType): void { + $dbUser = "owncloud"; + $dbHost = $dbType; + $dbName = "owncloud"; + $dbPass = "owncloud"; + + if ($dbType === "postgres") { + $dbType = "pgsql"; + } + if ($dbType === "oracle") { + $dbUser = "autotest"; + $dbType = "oci"; + } + + $this->invokingTheCommand("db:convert-type --password=$dbPass $dbType $dbUser $dbHost $dbName"); + $this->featureContext->setDbConversionState(true); + } + + /** + * This will run after EVERY scenario. + * It will set the properties for this object. + * + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function removeImportedCertificates():void { + $remainingCertificates = \array_diff($this->importedCertificates, $this->removedCertificates); + foreach ($remainingCertificates as $certificate) { + $this->invokingTheCommand("security:certificates:remove " . $certificate); + $this->theCommandShouldHaveBeenSuccessful(); + } + } + + /** + * This will run after EVERY scenario. + * It will set the properties for this object. + * + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function resetDAVTechPreview():void { + if ($this->doTechPreview) { + if ($this->initialTechPreviewStatus === "") { + SetupHelper::deleteSystemConfig( + 'dav.enable.tech_preview', + $this->featureContext->getStepLineRef() + ); + } elseif ($this->initialTechPreviewStatus === 'true' && !$this->techPreviewEnabled) { + $this->enableDAVTechPreview(); + } elseif ($this->initialTechPreviewStatus === 'false' && $this->techPreviewEnabled) { + $this->disableDAVTechPreview(); + } + } + } + + /** + * This will run after EVERY scenario. + * Some local_storage tests import storage from an export file. In that case + * we have not explicitly created the storage, and so we do not explicitly + * know to delete it. So delete the local storage that is known to be used + * in tests. + * + * @AfterScenario @local_storage + * + * @return void + * @throws Exception + */ + public function removeLocalStorageIfExists():void { + $this->deleteLocalStorageFolderUsingTheOccCommand('local_storage', false); + $this->deleteLocalStorageFolderUsingTheOccCommand('local_storage2', false); + $this->deleteLocalStorageFolderUsingTheOccCommand('local_storage3', false); + $this->deleteLocalStorageFolderUsingTheOccCommand('TestMountPoint', false); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + * @throws Exception + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + SetupHelper::init( + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + $ocVersion = SetupHelper::getSystemConfigValue( + 'version', + $this->featureContext->getStepLineRef() + ); + // dav.enable.tech_preview was used in some ownCloud versions before 10.4.0 + // only set it on those versions of ownCloud + if (\version_compare($ocVersion, '10.4.0') === -1) { + $this->doTechPreview = true; + $techPreviewEnabled = \trim( + SetupHelper::getSystemConfigValue( + 'dav.enable.tech_preview', + $this->featureContext->getStepLineRef() + ) + ); + $this->initialTechPreviewStatus = $techPreviewEnabled; + $this->techPreviewEnabled = $techPreviewEnabled === 'true'; + } + } + + /** + * @Then /^the system config should have dbtype set as "([^"]*)"$/ + * + * @param string $value + * + * @return void + * @throws GuzzleException + */ + public function theSystemConfigKeyShouldBeSetAs(string $value):void { + $actual_value = SetupHelper::getSystemConfigValue( + "dbtype", + $this->featureContext->getStepLineRef() + ); + $actual_value = \str_replace("\n", "", $actual_value); + Assert::assertEquals( + $value, + $actual_value, + "System config mismatched.\n + Expected dbType to be: " . $actual_value . "\n + Found: " . $value + ); + } + + /** + * @When the administrator lists migration status of app :app + * + * @param string $app + * + * @return void + * @throws Exception + */ + public function theAdministratorListsMigrationStatusOfApp(string $app):void { + $this->featureContext->setStdOutOfOccCommand(""); + $this->featureContext->setOccLastCode( + $this->featureContext->runOcc(['migrations:status', $app]) + ); + } + + /** + * @Then the following migration status should have been listed + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingMigrationStatusShouldHaveBeenListed(TableNode $table): void { + $actualOuput = $this->getMigrationStatusInfo(); + $expectedOutput = $table->getRowsHash(); + foreach ($expectedOutput as $key => $value) { + try { + $actualValue = $actualOuput[$key]; + } catch (Exception $e) { + Assert:: fail("Expected '$key' but not found!\nActual Migration status: " . \print_r($actualOuput, true)); + } + if ($this->isRegex($value)) { + $match = preg_match($value, $actualValue); + Assert:: assertEquals(1, $match, "Pattern '$value' is not matchable with value '$actualValue'"); + } else { + Assert:: assertEquals($value, $actualValue, "Expected '$key' to have value '$value' but got '$actualValue'"); + } + } + } + + /** + * @Then the Executed Migrations should equal the Available Migrations + * + * @return void + * @throws Exception + */ + public function theExecutedMigrationsShouldEqualTheAvailableMigrations(): void { + $migrationStatus = $this->getMigrationStatusInfo(); + Assert:: assertEquals($migrationStatus["Executed Migrations"], $migrationStatus["Available Migrations"], "The 'Executed Migration' is not same as 'Available Migration'"); + } + + /** + * @param string $value + * + * @return int + */ + public function isRegex($value) { + $regex = "/^\/[\s\S]+\/$/"; + return preg_match($regex, $value); + } + + /** + * @return void + */ + public function getMigrationStatusInfo() { + $commandOutput = $this->featureContext->getStdOutOfOccCommand(); + $migrationStatus = []; + if (!empty($commandOutput)) { + $infoArr = explode("\n", $commandOutput); + foreach ($infoArr as $info) { + if (!empty($info)) { + $row = \trim(\str_replace('>>', '', $info)); + $rowCol = explode(":", $row); + $migrationStatus[\trim($rowCol[0])] = \trim($rowCol[1]); + } + } + return $migrationStatus; + } else { + throwException("Migration status information is empty!"); + } + } +} diff --git a/tests/acceptance/features/bootstrap/Provisioning.php b/tests/acceptance/features/bootstrap/Provisioning.php new file mode 100644 index 00000000000..64ae80f9894 --- /dev/null +++ b/tests/acceptance/features/bootstrap/Provisioning.php @@ -0,0 +1,6141 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; +use TestHelpers\OcsApiHelper; +use TestHelpers\SetupHelper; +use TestHelpers\UserHelper; +use TestHelpers\HttpRequestHelper; +use TestHelpers\OcisHelper; +use TestHelpers\WebDavHelper; +use Laminas\Ldap\Exception\LdapException; +use Laminas\Ldap\Ldap; + +/** + * Functions for provisioning of users and groups + */ +trait Provisioning { + /** + * list of users that were created on the local server during test runs + * key is the lowercase username, value is an array of user attributes + * + * @var array + */ + private $createdUsers = []; + + /** + * list of users that were created on the remote server during test runs + * key is the lowercase username, value is an array of user attributes + * + * @var array + */ + private $createdRemoteUsers = []; + + /** + * @var array + */ + private $enabledApps = []; + + /** + * @var array + */ + private $disabledApps = []; + + /** + * @var array + */ + private $startingGroups = []; + + /** + * @var array + */ + private $createdRemoteGroups = []; + + /** + * @var array + */ + private $createdGroups = []; + + /** + * @var array + */ + private $userResponseFields = [ + "enabled", "quota", "email", "displayname", "home", "two_factor_auth_enabled", + "quota definition", "quota free", "quota user", "quota total", "quota relative" + ]; + + /** + * Check if this is the admin group. That group is always a local group in + * ownCloud10, even if other groups come from LDAP. + * + * @param string $groupname + * + * @return boolean + */ + public function isLocalAdminGroup(string $groupname):bool { + return ($groupname === "admin"); + } + + /** + * Usernames are not case-sensitive, and can generally be specified with any + * mix of upper and lower case. For remembering usernames use the normalized + * form so that "alice" and "Alice" are remembered as the same user. + * + * @param string|null $username + * + * @return string + */ + public function normalizeUsername(?string $username):string { + return \strtolower((string)$username); + } + + /** + * @return array + */ + public function getCreatedUsers():array { + return $this->createdUsers; + } + + /** + * @return boolean + */ + public function someUsersHaveBeenCreated():bool { + return (\count($this->createdUsers) > 0); + } + + /** + * @return array + */ + public function getCreatedGroups():array { + return $this->createdGroups; + } + + /** + * returns the display name of the current user + * if no "Display Name" is set the user-name is returned instead + * + * @return string + */ + public function getCurrentUserDisplayName():string { + return $this->getUserDisplayName($this->getCurrentUser()); + } + + /** + * returns the display name of a user + * if no "Display Name" is set the username is returned instead + * + * @param string $username + * + * @return string + */ + public function getUserDisplayName(string $username):string { + $normalizedUsername = $this->normalizeUsername($username); + if (isset($this->createdUsers[$normalizedUsername]['displayname'])) { + $displayName = (string) $this->createdUsers[$normalizedUsername]['displayname']; + if ($displayName !== '') { + return $displayName; + } + } + return $username; + } + + /** + * @param string $user + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function rememberUserDisplayName(string $user, string $displayName):void { + $normalizedUsername = $this->normalizeUsername($user); + if ($this->isAdminUsername($normalizedUsername)) { + $this->adminDisplayName = $displayName; + } else { + if ($this->currentServer === 'LOCAL') { + if (\array_key_exists($normalizedUsername, $this->createdUsers)) { + $this->createdUsers[$normalizedUsername]['displayname'] = $displayName; + } else { + throw new Exception( + __METHOD__ . " tried to remember display name '$displayName' for nonexistent local user '$user'" + ); + } + } elseif ($this->currentServer === 'REMOTE') { + if (\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + $this->createdRemoteUsers[$normalizedUsername]['displayname'] = $displayName; + } else { + throw new Exception( + __METHOD__ . " tried to remember display name '$displayName' for nonexistent federated user '$user'" + ); + } + } + } + } + + /** + * @param string $user + * @param string $emailAddress + * + * @return void + * @throws Exception + */ + public function rememberUserEmailAddress(string $user, string $emailAddress):void { + $normalizedUsername = $this->normalizeUsername($user); + if ($this->isAdminUsername($normalizedUsername)) { + $this->adminEmailAddress = $emailAddress; + } else { + if ($this->currentServer === 'LOCAL') { + if (\array_key_exists($normalizedUsername, $this->createdUsers)) { + $this->createdUsers[$normalizedUsername]['email'] = $emailAddress; + } else { + throw new Exception( + __METHOD__ . " tried to remember email address '$emailAddress' for nonexistent local user '$user'" + ); + } + } elseif ($this->currentServer === 'REMOTE') { + if (\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + $this->createdRemoteUsers[$normalizedUsername]['email'] = $emailAddress; + } else { + throw new Exception( + __METHOD__ . " tried to remember email address '$emailAddress' for nonexistent federated user '$user'" + ); + } + } + } + } + + /** + * returns an array of the user display names, keyed by normalized username + * if no "Display Name" is set the user-name is returned instead + * + * @return array + */ + public function getCreatedUserDisplayNames():array { + $result = []; + foreach ($this->getCreatedUsers() as $normalizedUsername => $user) { + $result[$normalizedUsername] = $this->getUserDisplayName($normalizedUsername); + } + return $result; + } + + /** + * @param string $user + * @param string $attribute + * + * @return mixed + * @throws Exception + */ + public function getAttributeOfCreatedUser(string $user, string $attribute) { + $usersList = $this->getCreatedUsers(); + $normalizedUsername = $this->normalizeUsername($user); + if (\array_key_exists($normalizedUsername, $usersList)) { + // provide attributes only if the user exists + if ($usersList[$normalizedUsername]["shouldExist"]) { + if (\array_key_exists($attribute, $usersList[$normalizedUsername])) { + return $usersList[$normalizedUsername][$attribute]; + } else { + throw new Exception( + __METHOD__ . ": User '$user' has no attribute with name '$attribute'." + ); + } + } else { + throw new Exception( + __METHOD__ . ": User '$user' has been deleted." + ); + } + } else { + throw new Exception( + __METHOD__ . ": User '$user' does not exist in the created list." + ); + } + } + + /** + * @param string $group + * @param string $attribute + * + * @return mixed + * @throws Exception + */ + public function getAttributeOfCreatedGroup(string $group, string $attribute) { + $groupsList = $this->getCreatedGroups(); + if (\array_key_exists($group, $groupsList)) { + // provide attributes only if the group exists + if ($groupsList[$group]["shouldExist"]) { + if (\array_key_exists($attribute, $groupsList[$group])) { + return $groupsList[$group][$attribute]; + } else { + throw new Exception( + __METHOD__ . ": Group '$group' has no attribute with name '$attribute'." + ); + } + } else { + throw new Exception( + __METHOD__ . ": Group '$group' has been deleted." + ); + } + } else { + throw new Exception( + __METHOD__ . ": Group '$group' does not exist in the created list." + ); + } + } + + /** + * returns an array of the group display names, keyed by group name + * currently group name and display name are always the same, so this + * function is a convenience for getting the group names in a similar + * format to what getCreatedUserDisplayNames() returns + * + * @return array + */ + public function getCreatedGroupDisplayNames():array { + $result = []; + foreach ($this->getCreatedGroups() as $groupName => $groupData) { + $result[$groupName] = $groupName; + } + return $result; + } + + /** + * + * @param string $username + * + * @return string password + * @throws Exception + */ + public function getUserPassword(string $username):string { + $normalizedUsername = $this->normalizeUsername($username); + if ($normalizedUsername === $this->getAdminUsername()) { + $password = $this->getAdminPassword(); + } elseif (\array_key_exists($normalizedUsername, $this->createdUsers)) { + $password = $this->createdUsers[$normalizedUsername]['password']; + } elseif (\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + $password = $this->createdRemoteUsers[$normalizedUsername]['password']; + } else { + throw new Exception( + "user '$username' was not created by this test run" + ); + } + + //make sure the function always returns a string + return (string) $password; + } + + /** + * + * @param string $username + * + * @return boolean + * @throws Exception + */ + public function theUserShouldExist(string $username):bool { + $normalizedUsername = $this->normalizeUsername($username); + if (\array_key_exists($normalizedUsername, $this->createdUsers)) { + return $this->createdUsers[$normalizedUsername]['shouldExist']; + } + + if (\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + return $this->createdRemoteUsers[$normalizedUsername]['shouldExist']; + } + + throw new Exception( + __METHOD__ + . " user '$username' was not created by this test run" + ); + } + + /** + * + * @param string $groupname + * + * @return boolean + * @throws Exception + */ + public function theGroupShouldExist(string $groupname):bool { + if (\array_key_exists($groupname, $this->createdGroups)) { + if (\array_key_exists('shouldExist', $this->createdGroups[$groupname])) { + return $this->createdGroups[$groupname]['shouldExist']; + } + return false; + } + + if (\array_key_exists($groupname, $this->createdRemoteGroups)) { + if (\array_key_exists('shouldExist', $this->createdRemoteGroups[$groupname])) { + return $this->createdRemoteGroups[$groupname]['shouldExist']; + } + return false; + } + + throw new Exception( + __METHOD__ + . " group '$groupname' was not created by this test run" + ); + } + + /** + * + * @param string $groupname + * + * @return boolean + * @throws Exception + */ + public function theGroupShouldBeAbleToBeDeleted(string $groupname):bool { + if (\array_key_exists($groupname, $this->createdGroups)) { + return $this->createdGroups[$groupname]['possibleToDelete'] ?? true; + } + + if (\array_key_exists($groupname, $this->createdRemoteGroups)) { + return $this->createdRemoteGroups[$groupname]['possibleToDelete'] ?? true; + } + + throw new Exception( + __METHOD__ + . " group '$groupname' was not created by this test run" + ); + } + + /** + * @When /^the administrator creates user "([^"]*)" using the provisioning API$/ + * + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function adminCreatesUserUsingTheProvisioningApi(?string $user):void { + $this->createUser( + $user, + null, + null, + null, + true, + 'api' + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has been created with default attributes in the database user backend$/ + * + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function userHasBeenCreatedOnDatabaseBackend(?string $user):void { + $this->adminCreatesUserUsingTheProvisioningApi($user); + $this->userShouldExist($user); + } + + /** + * @Given /^user "([^"]*)" has been created with default attributes and (tiny|small|large)\s?skeleton files$/ + * + * @param string $user + * @param string $skeletonType + * @param boolean $skeleton + * + * @return void + * @throws Exception + */ + public function userHasBeenCreatedWithDefaultAttributes( + string $user, + string $skeletonType = "", + bool $skeleton = true + ):void { + if ($skeletonType === "") { + $skeletonType = $this->getSmallestSkeletonDirName(); + } + + $originalSkeletonPath = $this->setSkeletonDirByType($skeletonType); + + try { + $this->createUser( + $user, + null, + null, + null, + true, + null, + true, + $skeleton + ); + $this->userShouldExist($user); + } finally { + $this->setSkeletonDir($originalSkeletonPath); + } + } + + /** + * @Given /^user "([^"]*)" has been created with default attributes and without skeleton files$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userHasBeenCreatedWithDefaultAttributesAndWithoutSkeletonFiles(string $user):void { + $this->userHasBeenCreatedWithDefaultAttributes($user); + $this->resetOccLastCode(); + } + + /** + * @Given these users have been created with default attributes and without skeleton files: + * expects a table of users with the heading + * "|username|" + * + * @param TableNode $table + * + * @return void + * @throws Exception|GuzzleException + */ + public function theseUsersHaveBeenCreatedWithDefaultAttributesAndWithoutSkeletonFiles(TableNode $table):void { + $originalSkeletonPath = $this->setSkeletonDirByType($this->getSmallestSkeletonDirName()); + try { + $this->createTheseUsers(true, true, true, $table); + } finally { + // restore skeleton directory even if user creation failed + $this->setSkeletonDir($originalSkeletonPath); + } + } + + /** + * @Given /^these users have been created without skeleton files ?(and not initialized|):$/ + * expects a table of users with the heading + * "|username|password|displayname|email|" + * password, displayname & email are optional + * + * @param TableNode $table + * @param string $doNotInitialize + * + * @return void + * @throws Exception + */ + public function theseUsersHaveBeenCreatedWithoutSkeletonFiles(TableNode $table, string $doNotInitialize):void { + $this->theseUsersHaveBeenCreated("", "", $doNotInitialize, $table); + } + + /** + * @Given the administrator has set the system language to :defaultLanguage + * + * @param string $defaultLanguage + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetTheSystemLanguageTo(string $defaultLanguage):void { + $this->runOcc( + ["config:system:set default_language --value $defaultLanguage"] + ); + } + + /** + * + * @param string $path + * + * @return void + */ + public function importLdifFile(string $path):void { + $ldifData = \file_get_contents($path); + $this->importLdifData($ldifData); + } + + /** + * imports an ldif string + * + * @param string $ldifData + * + * @return void + */ + public function importLdifData(string $ldifData):void { + $items = Laminas\Ldap\Ldif\Encoder::decode($ldifData); + if (isset($items['dn'])) { + //only one item in the ldif data + $this->ldap->add($items['dn'], $items); + } else { + foreach ($items as $item) { + if (isset($item["objectclass"])) { + if (\in_array("posixGroup", $item["objectclass"])) { + \array_push($this->ldapCreatedGroups, $item["cn"][0]); + $this->addGroupToCreatedGroupsList($item["cn"][0]); + } elseif (\in_array("inetOrgPerson", $item["objectclass"])) { + \array_push($this->ldapCreatedUsers, $item["uid"][0]); + $this->addUserToCreatedUsersList($item["uid"][0], $item["userpassword"][0]); + } + } + $this->ldap->add($item['dn'], $item); + } + } + } + + /** + * @param array $suiteParameters + * + * @return void + * @throws Exception + * @throws \LdapException + */ + public function connectToLdap(array $suiteParameters):void { + $useSsl = false; + + $this->ldapBaseDN = OcisHelper::getBaseDN(); + $this->ldapUsersOU = OcisHelper::getUsersOU(); + $this->ldapGroupsOU = OcisHelper::getGroupsOU(); + $this->ldapGroupSchema = OcisHelper::getGroupSchema(); + $this->ldapHost = OcisHelper::getHostname(); + $this->ldapPort = OcisHelper::getLdapPort(); + $useSsl = OcisHelper::useSsl(); + $this->ldapAdminUser = OcisHelper::getBindDN(); + $this->ldapAdminPassword = OcisHelper::getBindPassword(); + $this->skipImportLdif = (\getenv("REVA_LDAP_SKIP_LDIF_IMPORT") === "true"); + if ($useSsl === true) { + \putenv('LDAPTLS_REQCERT=never'); + } + + if ($this->ldapAdminPassword === "") { + $this->ldapAdminPassword = (string)$suiteParameters['ldapAdminPassword']; + } + $options = [ + 'host' => $this->ldapHost, + 'port' => $this->ldapPort, + 'password' => $this->ldapAdminPassword, + 'bindRequiresDn' => true, + 'useSsl' => $useSsl, + 'baseDn' => $this->ldapBaseDN, + 'username' => $this->ldapAdminUser + ]; + $this->ldap = new Ldap($options); + $this->ldap->bind(); + + $ldifFile = __DIR__ . (string)$suiteParameters['ldapInitialUserFilePath']; + if (OcisHelper::isTestingParallelDeployment()) { + $behatYml = \getenv("BEHAT_YML"); + if ($behatYml) { + $configPath = \dirname($behatYml); + $ldifFile = $configPath . "/" . \basename($ldifFile); + } + } + if (!$this->skipImportLdif) { + $this->importLdifFile($ldifFile); + } + $this->theLdapUsersHaveBeenResynced(); + } + + /** + * @Given the LDAP users have been resynced + * + * @return void + * @throws Exception + */ + public function theLdapUsersHaveBeenReSynced():void { + // we need to sync ldap users when testing for parallel deployment + if (!OcisHelper::isTestingOnOcisOrReva() || OcisHelper::isTestingParallelDeployment()) { + $occResult = SetupHelper::runOcc( + ['user:sync', 'OCA\User_LDAP\User_Proxy', '-m', 'remove'], + $this->getStepLineRef() + ); + if ($occResult['code'] !== "0") { + throw new Exception(__METHOD__ . " could not sync LDAP users " . $occResult['stdErr']); + } + } + } + + /** + * prepares suitable nested array with user-attributes for multiple users to be created + * + * @param boolean $setDefaultAttributes + * @param array $table + * + * @return array + * @throws JsonException + */ + public function buildUsersAttributesArray(bool $setDefaultAttributes, array $table):array { + $usersAttributes = []; + foreach ($table as $row) { + $userAttribute['userid'] = $this->getActualUsername($row['username']); + + if (isset($row['displayname'])) { + $userAttribute['displayName'] = $row['displayname']; + } elseif ($setDefaultAttributes) { + $userAttribute['displayName'] = $this->getDisplayNameForUser($row['username']); + if ($userAttribute['displayName'] === null) { + $userAttribute['displayName'] = $this->getDisplayNameForUser('regularuser'); + } + } else { + $userAttribute['displayName'] = null; + } + if (isset($row['email'])) { + $userAttribute['email'] = $row['email']; + } elseif ($setDefaultAttributes) { + $userAttribute['email'] = $this->getEmailAddressForUser($row['username']); + if ($userAttribute['email'] === null) { + $userAttribute['email'] = $row['username'] . '@owncloud.com'; + } + } else { + $userAttribute['email'] = null; + } + + if (isset($row['password'])) { + $userAttribute['password'] = $this->getActualPassword($row['password']); + } else { + $userAttribute['password'] = $this->getPasswordForUser($row['username']); + } + // Add request body to the bodies array. we will use that later to loop through created users. + $usersAttributes[] = $userAttribute; + } + return $usersAttributes; + } + + /** + * creates a user in the ldap server + * the created user is added to `createdUsersList` + * ldap users are re-synced after creating a new user + * + * @param array $setting + * + * @return void + * @throws Exception + */ + public function createLdapUser(array $setting):void { + $ou = $this->ldapUsersOU ; + // Some special characters need to be escaped in LDAP DN and attributes + // The special characters allowed in a username (UID) are +_.@- + // Of these, only + has to be escaped. + $userId = \str_replace('+', '\+', $setting["userid"]); + $newDN = 'uid=' . $userId . ',ou=' . $ou . ',' . $this->ldapBaseDN; + + //pick a high number as uidnumber to make sure there are no conflicts with existing uidnumbers + $uidNumber = \count($this->ldapCreatedUsers) + 30000; + $entry = []; + $entry['cn'] = $userId; + $entry['sn'] = $userId; + $entry['uid'] = $setting["userid"]; + $entry['homeDirectory'] = '/home/openldap/' . $setting["userid"]; + $entry['objectclass'][] = 'posixAccount'; + $entry['objectclass'][] = 'inetOrgPerson'; + $entry['objectclass'][] = 'organizationalPerson'; + $entry['objectclass'][] = 'person'; + $entry['objectclass'][] = 'top'; + + $entry['userPassword'] = $setting["password"]; + if (isset($setting["displayName"])) { + $entry['displayName'] = $setting["displayName"]; + } + if (isset($setting["email"])) { + $entry['mail'] = $setting["email"]; + } elseif (OcisHelper::isTestingOnOcis()) { + $entry['mail'] = $userId . '@owncloud.com'; + } + $entry['gidNumber'] = 5000; + $entry['uidNumber'] = $uidNumber; + + if (OcisHelper::isTestingOnOcis()) { + $entry['objectclass'][] = 'ownCloud'; + $entry['ownCloudUUID'] = WebDavHelper::generateUUIDv4(); + } + if (OcisHelper::isTestingParallelDeployment()) { + $entry['ownCloudSelector'] = $this->getOCSelector(); + } + + if ($this->federatedServerExists()) { + if (!\in_array($setting['userid'], $this->ldapCreatedUsers)) { + $this->ldap->add($newDN, $entry); + } + } else { + $this->ldap->add($newDN, $entry); + } + $this->ldapCreatedUsers[] = $setting["userid"]; + $this->theLdapUsersHaveBeenReSynced(); + } + + /** + * @param string $group group name + * + * @return void + * @throws Exception + * @throws LdapException + */ + public function createLdapGroup(string $group):void { + $baseDN = $this->getLdapBaseDN(); + $newDN = 'cn=' . $group . ',ou=' . $this->ldapGroupsOU . ',' . $baseDN; + $entry = []; + $entry['cn'] = $group; + $entry['objectclass'][] = 'top'; + + if ($this->ldapGroupSchema == "rfc2307") { + $entry['objectclass'][] = 'posixGroup'; + $entry['gidNumber'] = 5000; + } else { + $entry['objectclass'][] = 'groupOfNames'; + $entry['member'] = ""; + } + if (OcisHelper::isTestingOnOcis()) { + $entry['objectclass'][] = 'ownCloud'; + $entry['ownCloudUUID'] = WebDavHelper::generateUUIDv4(); + } + $this->ldap->add($newDN, $entry); + $this->ldapCreatedGroups[] = $group; + } + + /** + * + * @param string $configId + * @param string $configKey + * @param string $configValue + * + * @return void + * @throws Exception + */ + public function setLdapSetting(string $configId, string $configKey, string $configValue):void { + if ($configValue === "") { + $configValue = "''"; + } + $substitutions = [ + [ + "code" => "%ldap_host_without_scheme%", + "function" => [ + $this, + "getLdapHostWithoutScheme" + ], + "parameter" => [] + ], + [ + "code" => "%ldap_host%", + "function" => [ + $this, + "getLdapHost" + ], + "parameter" => [] + ], + [ + "code" => "%ldap_port%", + "function" => [ + $this, + "getLdapPort" + ], + "parameter" => [] + ] + ]; + $configValue = $this->substituteInLineCodes( + $configValue, + null, + [], + $substitutions + ); + $occResult = SetupHelper::runOcc( + ['ldap:set-config', $configId, $configKey, $configValue], + $this->getStepLineRef() + ); + if ($occResult['code'] !== "0") { + throw new Exception( + __METHOD__ . " could not set LDAP setting " . $occResult['stdErr'] + ); + } + } + + /** + * deletes LDAP users|groups created during test + * + * @return void + * @throws Exception + */ + public function deleteLdapUsersAndGroups():void { + $isOcisOrReva = OcisHelper::isTestingOnOcisOrReva(); + foreach ($this->ldapCreatedUsers as $user) { + if ($isOcisOrReva) { + $this->ldap->delete( + "uid=" . ldap_escape($user, "", LDAP_ESCAPE_DN) . ",ou=" . $this->ldapUsersOU . "," . $this->ldapBaseDN, + ); + } + $this->rememberThatUserIsNotExpectedToExist($user); + } + foreach ($this->ldapCreatedGroups as $group) { + if ($isOcisOrReva) { + $this->ldap->delete( + "cn=" . ldap_escape($group, "", LDAP_ESCAPE_DN) . ",ou=" . $this->ldapGroupsOU . "," . $this->ldapBaseDN, + ); + } + $this->rememberThatGroupIsNotExpectedToExist($group); + } + if (!$isOcisOrReva || !$this->skipImportLdif) { + //delete ou from LDIF import + $this->ldap->delete( + "ou=" . $this->ldapUsersOU . "," . $this->ldapBaseDN, + true + ); + //delete all created ldap groups + $this->ldap->delete( + "ou=" . $this->ldapGroupsOU . "," . $this->ldapBaseDN, + true + ); + } + $this->theLdapUsersHaveBeenResynced(); + } + + /** + * Sets back old settings + * + * @return void + * @throws Exception + */ + public function resetOldLdapConfig():void { + $toDeleteLdapConfig = $this->getToDeleteLdapConfigs(); + foreach ($toDeleteLdapConfig as $configId) { + SetupHelper::runOcc( + ['ldap:delete-config', $configId], + $this->getStepLineRef() + ); + } + foreach ($this->oldLdapConfig as $configId => $settings) { + foreach ($settings as $configKey => $configValue) { + $this->setLdapSetting($configId, $configKey, $configValue); + } + } + foreach ($this->toDeleteDNs as $dn) { + $this->getLdap()->delete($dn, true); + } + } + + /** + * Manually add skeleton files for a single user on OCIS and reva systems + * + * @param string $user + * @param string $password + * + * @return void + * @throws Exception + */ + public function manuallyAddSkeletonFilesForUser(string $user, string $password):void { + $settings = []; + $setting["userid"] = $user; + $setting["password"] = $password; + $settings[] = $setting; + $this->manuallyAddSkeletonFiles($settings); + } + + /** + * Manually add skeleton files on OCIS and reva systems + * + * @param array $usersAttributes + * + * @return void + * @throws Exception + */ + public function manuallyAddSkeletonFiles(array $usersAttributes):void { + if ($this->isEmptySkeleton()) { + // The empty skeleton has no files. There is nothing to do so return early. + return; + } + $skeletonDir = \getenv("SKELETON_DIR"); + $revaRoot = \getenv("OCIS_REVA_DATA_ROOT"); + $skeletonStrategy = \getenv("OCIS_SKELETON_STRATEGY"); + if (!$skeletonStrategy) { + $skeletonStrategy = 'upload'; //slower, but safer, so make it the default + } + if ($skeletonStrategy !== 'upload' && $skeletonStrategy !== 'copy') { + throw new Exception( + 'Wrong OCIS_SKELETON_STRATEGY environment variable. ' . + 'OCIS_SKELETON_STRATEGY has to be set to "upload" or "copy"' + ); + } + if (!$skeletonDir) { + throw new Exception('Missing SKELETON_DIR environment variable, cannot copy skeleton files for OCIS'); + } + if ($skeletonStrategy === 'copy' && !$revaRoot) { + throw new Exception( + 'OCIS_SKELETON_STRATEGY is set to "copy" ' . + 'but no "OCIS_REVA_DATA_ROOT" given' + ); + } + if ($skeletonStrategy === 'upload') { + foreach ($usersAttributes as $userAttributes) { + OcisHelper::recurseUpload( + $this->getBaseUrl(), + $skeletonDir, + $userAttributes['userid'], + $userAttributes['password'], + $this->getStepLineRef() + ); + } + } + if ($skeletonStrategy === 'copy') { + foreach ($usersAttributes as $userAttributes) { + $user = $userAttributes['userid']; + $dataDir = $revaRoot . "$user/files"; + if (!\file_exists($dataDir)) { + \mkdir($dataDir, 0777, true); + } + OcisHelper::recurseCopy($skeletonDir, $dataDir); + } + } + } + + /** + * This function will allow us to send user creation requests in parallel. + * This will be faster in comparison to waiting for each request to complete before sending another request. + * + * @param boolean $initialize + * @param array|null $usersAttributes + * @param string|null $method create the user with "ldap" or "api" + * @param boolean $skeleton + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function usersHaveBeenCreated( + bool $initialize, + ?array $usersAttributes, + ?string $method = null, + ?bool $skeleton = true + ) { + $requests = []; + $client = HttpRequestHelper::createClient( + $this->getAdminUsername(), + $this->getAdminPassword() + ); + + $useLdap = false; + $useGraph = false; + if ($method === null) { + $useLdap = $this->isTestingWithLdap(); + $useGraph = OcisHelper::isTestingWithGraphApi(); + } elseif ($method === "ldap") { + $useLdap = true; + } elseif ($method === "graph") { + $useGraph = true; + } + + foreach ($usersAttributes as $i => $userAttributes) { + if ($useLdap) { + $this->createLdapUser($userAttributes); + } else { + $attributesToCreateUser['userid'] = $userAttributes['userid']; + $attributesToCreateUser['password'] = $userAttributes['password']; + $attributesToCreateUser['displayname'] = $userAttributes['displayName']; + if (OcisHelper::isTestingOnOcisOrReva()) { + $attributesToCreateUser['username'] = $userAttributes['userid']; + if ($userAttributes['email'] === null) { + Assert::assertArrayHasKey( + 'userid', + $userAttributes, + __METHOD__ . " userAttributes array does not have key 'userid'" + ); + $attributesToCreateUser['email'] = $userAttributes['userid'] . '@owncloud.com'; + } else { + $attributesToCreateUser['email'] = $userAttributes['email']; + } + } + if ($useGraph) { + $body = \TestHelpers\GraphHelper::prepareCreateUserPayload( + $attributesToCreateUser['userid'], + $attributesToCreateUser['password'], + $attributesToCreateUser['email'], + $attributesToCreateUser['displayname'] + ); + $request = \TestHelpers\GraphHelper::createRequest( + $this->getBaseUrl(), + $this->getStepLineRef(), + "POST", + 'users', + $body, + ); + } else { + // Create a OCS request for creating the user. The request is not sent to the server yet. + $request = OcsApiHelper::createOcsRequest( + $this->getBaseUrl(), + 'POST', + "/cloud/users", + $this->stepLineRef, + $attributesToCreateUser + ); + } + // Add the request to the $requests array so that they can be sent in parallel. + $requests[] = $request; + } + } + + $exceptionToThrow = null; + if (!$useLdap) { + $results = HttpRequestHelper::sendBatchRequest($requests, $client); + // Check all requests to inspect failures. + foreach ($results as $key => $e) { + if ($e instanceof ClientException) { + if ($useGraph) { + $responseBody = $this->getJsonDecodedResponse($e->getResponse()); + $httpStatusCode = $e->getResponse()->getStatusCode(); + $graphStatusCode = $responseBody['error']['code']; + $messageText = $responseBody['error']['message']; + $exceptionToThrow = new Exception( + __METHOD__ . + " Unexpected failure when creating the user '" . + $usersAttributes[$key]['userid'] . "'" . + "\nHTTP status $httpStatusCode " . + "\nGraph status $graphStatusCode " . + "\nError message $messageText" + ); + } else { + $responseXml = $this->getResponseXml($e->getResponse(), __METHOD__); + $messageText = (string) $responseXml->xpath("/ocs/meta/message")[0]; + $ocsStatusCode = (string) $responseXml->xpath("/ocs/meta/statuscode")[0]; + $httpStatusCode = $e->getResponse()->getStatusCode(); + $reasonPhrase = $e->getResponse()->getReasonPhrase(); + $exceptionToThrow = new Exception( + __METHOD__ . " Unexpected failure when creating the user '" . + $usersAttributes[$key]['userid'] . "': HTTP status $httpStatusCode " . + "HTTP reason $reasonPhrase OCS status $ocsStatusCode " . + "OCS message $messageText" + ); + } + } + } + } + + // Create requests for setting displayname and email for the newly created users. + // These values cannot be set while creating the user, so we have to edit the newly created user to set these values. + $users = []; + $editData = []; + foreach ($usersAttributes as $userAttributes) { + $users[] = $userAttributes['userid']; + if ($useGraph) { + // for graph api, we need to save the user id to be able to add it in some group + // can be fetched with the "onPremisesSamAccountName" i.e. userid + $this->graphContext->adminHasRetrievedUserUsingTheGraphApi($userAttributes['userid']); + $userAttributes['id'] = $this->getJsonDecodedResponse()['id']; + } else { + $userAttributes['id'] = null; + } + $this->addUserToCreatedUsersList( + $userAttributes['userid'], + $userAttributes['password'], + $userAttributes['displayName'], + $userAttributes['email'], + $userAttributes['id'] + ); + + if (OcisHelper::isTestingOnOcisOrReva()) { + OcisHelper::createEOSStorageHome( + $this->getBaseUrl(), + $userAttributes['userid'], + $userAttributes['password'], + $this->getStepLineRef() + ); + // We don't need to set displayName and email while running in oCIS + // As they are set when creating the user + continue; + } + if (isset($userAttributes['displayName'])) { + $editData[] = ['user' => $userAttributes['userid'], 'key' => 'displayname', 'value' => $userAttributes['displayName']]; + } + if (isset($userAttributes['email'])) { + $editData[] = ['user' => $userAttributes['userid'], 'key' => 'email', 'value' => $userAttributes['email']]; + } + } + // Edit the users in parallel to make the process faster. + if (!OcisHelper::isTestingOnOcisOrReva() && !$useLdap && \count($editData) > 0) { + UserHelper::editUserBatch( + $this->getBaseUrl(), + $editData, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->stepLineRef + ); + } + + if (isset($exceptionToThrow)) { + throw $exceptionToThrow; + } + + // If the user should have skeleton files, and we are testing on OCIS + // then do some work to "manually" put the skeleton files in place. + // When testing on ownCloud 10 the user is already getting whatever + // skeleton dir is defined in the server-under-test. + if ($skeleton && OcisHelper::isTestingOnOcisOrReva()) { + $this->manuallyAddSkeletonFiles($usersAttributes); + } + + if ($initialize && ($this->isEmptySkeleton() || !OcisHelper::isTestingOnOcis())) { + // We need to initialize each user using the individual authentication of each user. + // That is not possible in Guzzle6 batch mode. So we do it with normal requests in serial. + $this->initializeUsers($users); + } + } + + /** + * @When /^the administrator creates these users with ?(default attributes and|) skeleton files ?(but not initialized|):$/ + * + * expects a table of users with the heading + * "|username|password|displayname|email|" + * password, displayname & email are optional + * + * @param string $setDefaultAttributes + * @param string $doNotInitialize + * @param TableNode $table + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function theAdministratorCreatesTheseUsers( + string $setDefaultAttributes, + string $doNotInitialize, + TableNode $table + ): void { + $this->verifyTableNodeColumns($table, ['username'], ['displayname', 'email', 'password']); + $table = $table->getColumnsHash(); + $setDefaultAttributes = $setDefaultAttributes !== ""; + $initialize = $doNotInitialize === ""; + $usersAttributes = $this->buildUsersAttributesArray($setDefaultAttributes, $table); + $this->usersHaveBeenCreated( + $initialize, + $usersAttributes + ); + } + + /** + * expects a table of users with the heading + * "|username|password|displayname|email|" + * password, displayname & email are optional + * + * @param boolean $setDefaultAttributes + * @param boolean $initialize + * @param boolean $skeleton + * @param TableNode $table + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function createTheseUsers(bool $setDefaultAttributes, bool $initialize, bool $skeleton, TableNode $table):void { + $this->verifyTableNodeColumns($table, ['username'], ['displayname', 'email', 'password']); + $table = $table->getColumnsHash(); + $usersAttributes = $this->buildUsersAttributesArray($setDefaultAttributes, $table); + $this->usersHaveBeenCreated( + $initialize, + $usersAttributes, + null, + $skeleton + ); + foreach ($usersAttributes as $expectedUser) { + $this->userShouldExist($expectedUser["userid"]); + } + } + + /** + * @Given /^these users have been created with ?(default attributes and|) (tiny|small|large)\s?skeleton files ?(but not initialized|):$/ + * + * expects a table of users with the heading + * "|username|password|displayname|email|" + * password, displayname & email are optional + * + * @param string $defaultAttributesText + * @param string $skeletonType + * @param string $doNotInitialize + * @param TableNode $table + * + * @return void + * @throws Exception|GuzzleException + */ + public function theseUsersHaveBeenCreated( + string $defaultAttributesText, + string $skeletonType, + string $doNotInitialize, + TableNode $table + ):void { + if ($skeletonType === "") { + $skeletonType = $this->getSmallestSkeletonDirName(); + } + + $originalSkeletonPath = $this->setSkeletonDirByType($skeletonType); + $setDefaultAttributes = $defaultAttributesText !== ""; + $initialize = $doNotInitialize === ""; + try { + $this->createTheseUsers($setDefaultAttributes, $initialize, true, $table); + } finally { + // The effective skeleton directory is the one when the user is initialized + // If we did not initialize the user on creation, then we need to leave + // the skeleton directory in effect so that it applies when some action + // happens later in the scenario that causes the user to be initialized. + if ($initialize) { + $this->setSkeletonDir($originalSkeletonPath); + } + } + } + + /** + * @When the administrator changes the password of user :user to :password using the provisioning API + * + * @param string $user + * @param string $password + * + * @return void + * @throws Exception + */ + public function adminChangesPasswordOfUserToUsingTheProvisioningApi( + string $user, + string $password + ):void { + $this->response = UserHelper::editUser( + $this->getBaseUrl(), + $user, + 'password', + $password, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef() + ); + } + + /** + * @Given the administrator has changed the password of user :user to :password + * + * @param string $user + * @param string $password + * + * @return void + * @throws Exception + */ + public function adminHasChangedPasswordOfUserTo( + string $user, + string $password + ):void { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminChangesPasswordOfUserToUsingTheGraphApi( + $user, + $password + ); + } else { + $this->adminChangesPasswordOfUserToUsingTheProvisioningApi( + $user, + $password + ); + } + $this->theHTTPStatusCodeShouldBe( + 200, + "could not change password of user $user" + ); + } + + /** + * @When /^user "([^"]*)" (enables|disables) app "([^"]*)"$/ + * + * @param string $user + * @param string $action enables or disables + * @param string $app + * + * @return void + */ + public function userEnablesOrDisablesApp(string $user, string $action, string $app):void { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/apps/$app"; + if ($action === 'enables') { + $this->response = HttpRequestHelper::post( + $fullUrl, + $this->getStepLineRef(), + $user, + $this->getPasswordForUser($user) + ); + } else { + $this->response = HttpRequestHelper::delete( + $fullUrl, + $this->getStepLineRef(), + $user, + $this->getPasswordForUser($user) + ); + } + } + + /** + * @When /^the administrator (enables|disables) app "([^"]*)"$/ + * + * @param string $action enables or disables + * @param string $app + * + * @return void + */ + public function adminEnablesOrDisablesApp(string $action, string $app):void { + $this->userEnablesOrDisablesApp( + $this->getAdminUsername(), + $action, + $app + ); + } + + /** + * @Given /^app "([^"]*)" has been (enabled|disabled)$/ + * + * @param string $app + * @param string $action enabled or disabled + * + * @return void + */ + public function appHasBeenDisabled(string $app, string $action):void { + if ($action === 'enabled') { + $action = 'enables'; + } else { + $action = 'disables'; + } + $this->userEnablesOrDisablesApp( + $this->getAdminUsername(), + $action, + $app + ); + } + + /** + * @When the administrator gets the info of app :app + * + * @param string $app + * + * @return void + */ + public function theAdministratorGetsTheInfoOfApp(string $app):void { + $this->ocsContext->userSendsToOcsApiEndpoint( + $this->getAdminUsername(), + "GET", + "/cloud/apps/$app" + ); + } + + /** + * @When the administrator gets all apps using the provisioning API + * + * @return void + */ + public function theAdministratorGetsAllAppsUsingTheProvisioningApi():void { + $this->getAllApps(); + } + + /** + * @When the administrator gets all enabled apps using the provisioning API + * + * @return void + */ + public function theAdministratorGetsAllEnabledAppsUsingTheProvisioningApi():void { + $this->getEnabledApps(); + } + + /** + * @When the administrator gets all disabled apps using the provisioning API + * + * @return void + */ + public function theAdministratorGetsAllDisabledAppsUsingTheProvisioningApi():void { + $this->getDisabledApps(); + } + + /** + * @When the administrator sends a user creation request with the following attributes using the provisioning API: + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function adminSendsUserCreationRequestWithFollowingAttributesUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeRows($table, ["username", "password"], ["email", "displayname"]); + $table = $table->getRowsHash(); + $username = $this->getActualUsername($table["username"]); + $password = $this->getActualPassword($table["password"]); + $displayname = \array_key_exists("displayname", $table) ? $table["displayname"] : null; + $email = \array_key_exists("email", $table) ? $table["email"] : null; + + if (OcisHelper::isTestingOnOcisOrReva()) { + if ($email === null) { + $email = $username . '@owncloud.com'; + } + } + + $userAttributes = [ + ["userid", $username], + ["password", $password], + ]; + + if ($displayname !== null) { + $userAttributes[] = ["displayname", $displayname]; + } + + if ($email !== null) { + $userAttributes[] = ["email", $email]; + } + + if (OcisHelper::isTestingOnOcisOrReva()) { + $userAttributes[] = ["username", $username]; + } + + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $this->getAdminUsername(), + "POST", + "/cloud/users", + new TableNode($userAttributes) + ); + $this->addUserToCreatedUsersList( + $username, + $password, + $displayname, + $email, + null, + $this->theHTTPStatusCodeWasSuccess() + ); + if (OcisHelper::isTestingOnOcisOrReva()) { + $this->manuallyAddSkeletonFilesForUser($username, $password); + } + } + + /** + * @When /^the administrator sends a user creation request for user "([^"]*)" password "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $password + * + * @return void + * @throws Exception + */ + public function adminSendsUserCreationRequestUsingTheProvisioningApi(string $user, string $password):void { + $user = $this->getActualUsername($user); + $password = $this->getActualPassword($password); + if (OcisHelper::isTestingOnOcisOrReva()) { + $email = $user . '@owncloud.com'; + $bodyTable = new TableNode( + [ + ['userid', $user], + ['password', $password], + ['username', $user], + ['email', $email] + ] + ); + } else { + $email = null; + $bodyTable = new TableNode([['userid', $user], ['password', $password]]); + } + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $this->getAdminUsername(), + "POST", + "/cloud/users", + $bodyTable + ); + $this->pushToLastStatusCodesArrays(); + $success = $this->theHTTPStatusCodeWasSuccess(); + $this->addUserToCreatedUsersList( + $user, + $password, + null, + $email, + null, + $success + ); + if (OcisHelper::isTestingOnOcisOrReva() && $success) { + OcisHelper::createEOSStorageHome( + $this->getBaseUrl(), + $user, + $password, + $this->getStepLineRef() + ); + $this->manuallyAddSkeletonFilesForUser($user, $password); + } + } + + /** + * @When /^the administrator sends a user creation request for the following users with password using the provisioning API$/ + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorSendsAUserCreationRequestForTheFollowingUsersWithPasswordUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username", "password"], ["comment"]); + $users = $table->getHash(); + foreach ($users as $user) { + $this->adminSendsUserCreationRequestUsingTheProvisioningApi($user["username"], $user["password"]); + } + } + + /** + * @When /^unauthorized user "([^"]*)" tries to create new user "([^"]*)" with password "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $userToCreate + * @param string $password + * + * @return void + * @throws Exception + */ + public function userSendsUserCreationRequestUsingTheProvisioningApi(string $user, string $userToCreate, string $password):void { + $userToCreate = $this->getActualUsername($userToCreate); + $password = $this->getActualPassword($password); + if (OcisHelper::isTestingOnOcisOrReva()) { + $email = $userToCreate . '@owncloud.com'; + $bodyTable = new TableNode( + [ + ['userid', $userToCreate], + ['password', $password], + ['username', $userToCreate], + ['email', $email] + ] + ); + } else { + $email = null; + $bodyTable = new TableNode([['userid', $userToCreate], ['password', $password]]); + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "POST", + "/cloud/users", + $bodyTable + ); + $this->addUserToCreatedUsersList( + $userToCreate, + $password, + null, + $email, + null, + $this->theHTTPStatusCodeWasSuccess() + ); + } + + /** + * @When /^the administrator sends a user creation request for user "([^"]*)" password "([^"]*)" group "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $password + * @param string $group + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesUserPasswordGroupUsingTheProvisioningApi( + string $user, + string $password, + string $group + ):void { + $user = $this->getActualUsername($user); + $password = $this->getActualPassword($password); + if (OcisHelper::isTestingOnOcisOrReva()) { + $email = $user . '@owncloud.com'; + $bodyTable = new TableNode( + [ + ['userid', $user], + ['password', $password], + ['username', $user], + ['email', $email], + ['groups[]', $group], + ] + ); + } else { + $email = null; + $bodyTable = new TableNode( + [['userid', $user], ['password', $password], ['groups[]', $group]] + ); + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $this->getAdminUsername(), + "POST", + "/cloud/users", + $bodyTable + ); + $this->addUserToCreatedUsersList( + $user, + $password, + null, + $email, + null, + $this->theHTTPStatusCodeWasSuccess() + ); + if (OcisHelper::isTestingOnOcisOrReva()) { + $this->manuallyAddSkeletonFilesForUser($user, $password); + } + } + + /** + * @When /^the groupadmin "([^"]*)" sends a user creation request for user "([^"]*)" password "([^"]*)" group "([^"]*)" using the provisioning API$/ + * + * @param string $groupadmin + * @param string $userToCreate + * @param string $password + * @param string $group + * + * @return void + * @throws Exception + */ + public function theGroupAdminCreatesUserPasswordGroupUsingTheProvisioningApi( + string $groupadmin, + string $userToCreate, + string $password, + string $group + ):void { + $userToCreate = $this->getActualUsername($userToCreate); + $password = $this->getActualPassword($password); + if (OcisHelper::isTestingOnOcisOrReva()) { + $email = $userToCreate . '@owncloud.com'; + $bodyTable = new TableNode( + [ + ['userid', $userToCreate], + ['password', $userToCreate], + ['username', $userToCreate], + ['email', $email], + ['groups[]', $group], + ] + ); + } else { + $email = null; + $bodyTable = new TableNode( + [['userid', $userToCreate], ['password', $password], ['groups[]', $group]] + ); + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $groupadmin, + "POST", + "/cloud/users", + $bodyTable + ); + $this->addUserToCreatedUsersList( + $userToCreate, + $password, + null, + $email, + null, + $this->theHTTPStatusCodeWasSuccess() + ); + if (OcisHelper::isTestingOnOcisOrReva()) { + $this->manuallyAddSkeletonFilesForUser($userToCreate, $password); + } + } + + /** + * @When /^the groupadmin "([^"]*)" tries to create new user "([^"]*)" password "([^"]*)" in other group "([^"]*)" using the provisioning API$/ + * + * @param string $groupadmin + * @param string $userToCreate + * @param string|null $password + * @param string $group + * + * @return void + */ + public function theGroupAdminCreatesUserPasswordInOtherGroupUsingTheProvisioningApi( + string $groupadmin, + string $userToCreate, + ?string $password, + string $group + ):void { + $userToCreate = $this->getActualUsername($userToCreate); + $password = $this->getActualPassword($password); + if (OcisHelper::isTestingOnOcisOrReva()) { + $email = $userToCreate . '@owncloud.com'; + $bodyTable = new TableNode( + [ + ['userid', $userToCreate], + ['password', $userToCreate], + ['username', $userToCreate], + ['email', $email], + ['groups[]', $group], + ] + ); + } else { + $email = null; + $bodyTable = new TableNode( + [['userid', $userToCreate], ['password', $password], ['groups[]', $group]] + ); + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $groupadmin, + "POST", + "/cloud/users", + $bodyTable + ); + $this->addUserToCreatedUsersList( + $userToCreate, + $password, + null, + $email, + null, + $this->theHTTPStatusCodeWasSuccess() + ); + } + + /** + * @param string $username + * @param string|null $password + * + * @return void + */ + public function resetUserPasswordAsAdminUsingTheProvisioningApi(string $username, ?string $password):void { + $this->userResetUserPasswordUsingProvisioningApi( + $this->getAdminUsername(), + $username, + $password + ); + } + + /** + * @When the administrator resets the password of user :username to :password using the provisioning API + * + * @param string $username of the user whose password is reset + * @param string|null $password + * + * @return void + */ + public function adminResetsPasswordOfUserUsingTheProvisioningApi(string $username, ?string $password):void { + $this->resetUserPasswordAsAdminUsingTheProvisioningApi( + $username, + $password + ); + } + + /** + * @Given the administrator has reset the password of user :username to :password + * + * @param string $username of the user whose password is reset + * @param string $password + * + * @return void + */ + public function adminHasResetPasswordOfUserUsingTheProvisioningApi(string $username, ?string $password):void { + $this->resetUserPasswordAsAdminUsingTheProvisioningApi( + $username, + $password + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string|null $user + * @param string|null $username + * @param string|null $password + * + * @return void + */ + public function userResetUserPasswordUsingProvisioningApi(?string $user, ?string $username, ?string $password):void { + $targetUsername = $this->getActualUsername($username); + $password = $this->getActualPassword($password); + $this->userTriesToResetUserPasswordUsingTheProvisioningApi( + $user, + $targetUsername, + $password + ); + $this->rememberUserPassword($targetUsername, $password); + } + + /** + * @When user :user resets the password of user :username to :password using the provisioning API + * + * @param string|null $user that does the password reset + * @param string|null $username of the user whose password is reset + * @param string|null $password + * + * @return void + */ + public function userResetsPasswordOfUserUsingTheProvisioningApi(?string $user, ?string $username, ?string $password):void { + $this->userResetUserPasswordUsingProvisioningApi( + $user, + $username, + $password + ); + } + + /** + * @Given user :user has reset the password of user :username to :password + * + * @param string|null $user that does the password reset + * @param string|null $username of the user whose password is reset + * @param string|null $password + * + * @return void + */ + public function userHasResetPasswordOfUserUsingTheProvisioningApi(?string $user, ?string $username, ?string $password):void { + $this->userResetUserPasswordUsingProvisioningApi( + $user, + $username, + $password + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string|null $user + * @param string|null $username + * @param string|null $password + * + * @return void + */ + public function userTriesToResetUserPasswordUsingTheProvisioningApi(?string $user, ?string $username, ?string $password):void { + $password = $this->getActualPassword($password); + $bodyTable = new TableNode([['key', 'password'], ['value', $password]]); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "PUT", + "/cloud/users/$username", + $bodyTable + ); + } + + /** + * @When user :user tries to reset the password of user :username to :password using the provisioning API + * + * @param string|null $user that does the password reset + * @param string|null $username of the user whose password is reset + * @param string|null $password + * + * @return void + */ + public function userTriesToResetPasswordOfUserUsingTheProvisioningApi(?string $user, ?string $username, ?string $password):void { + $this->userTriesToResetUserPasswordUsingTheProvisioningApi( + $user, + $username, + $password + ); + } + + /** + * @Given user :user has tried to reset the password of user :username to :password + * + * @param string|null $user that does the password reset + * @param string|null $username of the user whose password is reset + * @param string|null $password + * + * @return void + */ + public function userHasTriedToResetPasswordOfUserUsingTheProvisioningApi(?string $user, ?string $username, ?string $password):void { + $this->userTriesToResetUserPasswordUsingTheProvisioningApi( + $user, + $username, + $password + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Given /^the administrator has deleted user "([^"]*)" using the provisioning API$/ + * + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function theAdministratorHasDeletedUserUsingTheProvisioningApi(?string $user):void { + $user = $this->getActualUsername($user); + $this->deleteTheUserUsingTheProvisioningApi($user); + WebDavHelper::removeSpaceIdReferenceForUser($user); + $this->userShouldNotExist($user); + } + + /** + * @When /^the administrator deletes user "([^"]*)" using the provisioning API$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdminDeletesUserUsingTheProvisioningApi(string $user):void { + $user = $this->getActualUsername($user); + $this->deleteTheUserUsingTheProvisioningApi($user); + $this->rememberThatUserIsNotExpectedToExist($user); + } + + /** + * @When the administrator deletes the following users using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesTheFollowingUsersUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->theAdminDeletesUserUsingTheProvisioningApi($username["username"]); + } + } + + /** + * @When user :user deletes user :otherUser using the provisioning API + * + * @param string $user + * @param string $otherUser + * + * @return void + * @throws Exception + */ + public function userDeletesUserUsingTheProvisioningApi( + string $user, + string $otherUser + ):void { + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $actualOtherUser = $this->getActualUsername($otherUser); + + $this->response = UserHelper::deleteUser( + $this->getBaseUrl(), + $actualOtherUser, + $actualUser, + $actualPassword, + $this->getStepLineRef(), + $this->ocsApiVersion + ); + } + + /** + * @When /^the administrator changes the email of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $email + * + * @return void + * @throws Exception + */ + public function adminChangesTheEmailOfUserToUsingTheProvisioningApi( + string $user, + string $email + ):void { + $user = $this->getActualUsername($user); + $this->response = UserHelper::editUser( + $this->getBaseUrl(), + $user, + 'email', + $email, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->rememberUserEmailAddress($user, $email); + } + + /** + * @Given /^the administrator has changed the email of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $email + * + * @return void + * @throws Exception + */ + public function adminHasChangedTheEmailOfUserTo(string $user, string $email):void { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userHasBeenEditedUsingTheGraphApi( + $user, + null, + null, + $email + ); + $updatedUserData = $this->getJsonDecodedResponse(); + Assert::assertEquals( + $email, + $updatedUserData['mail'] + ); + } else { + $this->adminChangesTheEmailOfUserToUsingTheProvisioningApi( + $user, + $email + ); + $this->theHTTPStatusCodeShouldBe( + 200, + "could not change email of user $user" + ); + } + } + + /** + * @Given the administrator has changed their own email address to :email + * + * @param string|null $email + * + * @return void + * @throws Exception + */ + public function theAdministratorHasChangedTheirOwnEmailAddressTo(?string $email):void { + $admin = $this->getAdminUsername(); + $this->adminHasChangedTheEmailOfUserTo($admin, $email); + } + + /** + * @param string $requestingUser + * @param string $targetUser + * @param string $email + * + * @return void + * @throws JsonException + */ + public function userChangesUserEmailUsingProvisioningApi( + string $requestingUser, + string $targetUser, + string $email + ):void { + $this->response = UserHelper::editUser( + $this->getBaseUrl(), + $this->getActualUsername($targetUser), + 'email', + $email, + $this->getActualUsername($requestingUser), + $this->getPasswordForUser($requestingUser), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + } + + /** + * @When /^user "([^"]*)" changes the email of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $email + * + * @return void + * @throws Exception + */ + public function userChangesTheEmailOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $email + ):void { + $this->userTriesToChangeTheEmailOfUserUsingTheProvisioningApi( + $requestingUser, + $targetUser, + $email + ); + $targetUser = $this->getActualUsername($targetUser); + $this->rememberUserEmailAddress($targetUser, $email); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" tries to change the email of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $email + * + * @return void + */ + public function userTriesToChangeTheEmailOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $email + ):void { + $requestingUser = $this->getActualUsername($requestingUser); + $targetUser = $this->getActualUsername($targetUser); + $this->userChangesUserEmailUsingProvisioningApi( + $requestingUser, + $targetUser, + $email + ); + } + + /** + * @Given /^user "([^"]*)" has changed the email of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $email + * + * @return void + * @throws Exception + */ + public function userHasChangedTheEmailOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $email + ):void { + $requestingUser = $this->getActualUsername($requestingUser); + $targetUser = $this->getActualUsername($targetUser); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userHasBeenEditedUsingTheGraphApi( + $targetUser, + null, + null, + $email, + null, + $requestingUser, + $this->getPasswordForUser($requestingUser) + ); + $updatedUserData = $this->getJsonDecodedResponse(); + Assert::assertEquals( + $email, + $updatedUserData['mail'], + ); + } else { + $this->userChangesUserEmailUsingProvisioningApi( + $requestingUser, + $targetUser, + $email + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + $this->rememberUserEmailAddress($targetUser, $email); + } + + /** + * Edit the "display name" of a user by sending the key "displayname" to the API end point. + * + * This is the newer and consistent key name. + * + * @see https://github.com/owncloud/core/pull/33040 + * + * @When /^the administrator changes the display name of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function adminChangesTheDisplayNameOfUserUsingTheProvisioningApi( + string $user, + string $displayName + ):void { + $user = $this->getActualUsername($user); + $this->adminChangesTheDisplayNameOfUserUsingKey( + $user, + 'displayname', + $displayName + ); + $this->rememberUserDisplayName($user, $displayName); + } + + /** + * @Given /^the administrator has changed the display name of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function adminHasChangedTheDisplayNameOfUser( + string $user, + string $displayName + ):void { + $user = $this->getActualUsername($user); + if ($this->isTestingWithLdap()) { + $this->editLdapUserDisplayName( + $user, + $displayName + ); + } elseif (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userHasBeenEditedUsingTheGraphApi( + $user, + null, + null, + null, + $displayName + ); + $updatedUserData = $this->getJsonDecodedResponse(); + Assert::assertEquals( + $displayName, + $updatedUserData['displayName'] + ); + } else { + $this->adminChangesTheDisplayNameOfUserUsingKey( + $user, + 'displayname', + $displayName + ); + $response = UserHelper::getUser( + $this->getBaseUrl(), + $user, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef() + ); + $this->setResponse($response); + $this->theDisplayNameReturnedByTheApiShouldBe($displayName); + } + $this->rememberUserDisplayName($user, $displayName); + } + + /** + * As the administrator, edit the "display name" of a user by sending the key "display" to the API end point. + * + * This is the older and inconsistent key name, which remains for backward-compatibility. + * + * @see https://github.com/owncloud/core/pull/33040 + * + * @When /^the administrator changes the display of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function adminChangesTheDisplayOfUserUsingTheProvisioningApi( + string $user, + string $displayName + ):void { + $user = $this->getActualUsername($user); + $this->adminChangesTheDisplayNameOfUserUsingKey( + $user, + 'display', + $displayName + ); + $this->rememberUserDisplayName($user, $displayName); + } + + /** + * + * @param string $user + * @param string $key + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function adminChangesTheDisplayNameOfUserUsingKey( + string $user, + string $key, + string $displayName + ):void { + $result = UserHelper::editUser( + $this->getBaseUrl(), + $this->getActualUsername($user), + $key, + $displayName, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + if ($result->getStatusCode() !== 200) { + throw new Exception( + __METHOD__ . " could not change display name of user using key $key " + . $result->getStatusCode() . " " . $result->getBody() + ); + } + } + + /** + * As a user, edit the "display name" of a user by sending the key "displayname" to the API end point. + * + * This is the newer and consistent key name. + * + * @see https://github.com/owncloud/core/pull/33040 + * + * @When /^user "([^"]*)" changes the display name of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function userChangesTheDisplayNameOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $displayName + ):void { + $this->userTriesToChangeTheDisplayNameOfUserUsingTheProvisioningApi( + $requestingUser, + $targetUser, + $displayName + ); + $targetUser = $this->getActualUsername($targetUser); + $this->rememberUserDisplayName($targetUser, $displayName); + } + + /** + * As a user, try to edit the "display name" of a user by sending the key "displayname" to the API end point. + * + * This is the newer and consistent key name. + * + * @see https://github.com/owncloud/core/pull/33040 + * + * @When /^user "([^"]*)" tries to change the display name of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $displayName + * + * @return void + */ + public function userTriesToChangeTheDisplayNameOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $displayName + ):void { + $requestingUser = $this->getActualUsername($requestingUser); + $targetUser = $this->getActualUsername($targetUser); + $this->userChangesTheDisplayNameOfUserUsingKey( + $requestingUser, + $targetUser, + 'displayname', + $displayName + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * As a user, edit the "display name" of a user by sending the key "display" to the API end point. + * + * This is the older and inconsistent key name. + * + * @see https://github.com/owncloud/core/pull/33040 + * + * @When /^user "([^"]*)" changes the display of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function userChangesTheDisplayOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $displayName + ):void { + $requestingUser = $this->getActualUsername($requestingUser); + $targetUser = $this->getActualUsername($targetUser); + $this->userChangesTheDisplayNameOfUserUsingKey( + $requestingUser, + $targetUser, + 'display', + $displayName + ); + $this->rememberUserDisplayName($targetUser, $displayName); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has changed the display name of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $displayName + * + * @return void + * @throws Exception + */ + public function userHasChangedTheDisplayNameOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $displayName + ):void { + $requestingUser = $this->getActualUsername($requestingUser); + $targetUser = $this->getActualUsername($targetUser); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userHasBeenEditedUsingTheGraphApi( + $targetUser, + null, + null, + null, + $displayName, + $requestingUser, + $this->getPasswordForUser($requestingUser) + ); + $updatedUserData = $this->getJsonDecodedResponse(); + Assert::assertEquals( + $displayName, + $updatedUserData['displayName'] + ); + } else { + $this->userChangesTheDisplayNameOfUserUsingKey( + $requestingUser, + $targetUser, + 'displayname', + $displayName + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + $this->rememberUserDisplayName($targetUser, $displayName); + } + /** + * + * @param string $requestingUser + * @param string $targetUser + * @param string $key + * @param string $displayName + * + * @return void + */ + public function userChangesTheDisplayNameOfUserUsingKey( + string $requestingUser, + string $targetUser, + string $key, + string $displayName + ):void { + $result = UserHelper::editUser( + $this->getBaseUrl(), + $this->getActualUsername($targetUser), + $key, + $displayName, + $this->getActualUsername($requestingUser), + $this->getPasswordForUser($requestingUser), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @When /^the administrator changes the quota of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $quota + * + * @return void + */ + public function adminChangesTheQuotaOfUserUsingTheProvisioningApi( + string $user, + string $quota + ):void { + $result = UserHelper::editUser( + $this->getBaseUrl(), + $this->getActualUsername($user), + 'quota', + $quota, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @Given /^the administrator has (?:changed|set) the quota of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $quota + * + * @return void + */ + public function adminHasChangedTheQuotaOfUserTo( + string $user, + string $quota + ):void { + $user = $this->getActualUsername($user); + $this->adminChangesTheQuotaOfUserUsingTheProvisioningApi( + $user, + $quota + ); + $this->theHTTPStatusCodeShouldBe( + 200, + "could not change quota of user $user" + ); + } + + /** + * @param string $requestingUser + * @param string $targetUser + * @param string $quota + * + * @return void + */ + public function userChangeQuotaOfUserUsingProvisioningApi( + string $requestingUser, + string $targetUser, + string $quota + ):void { + $result = UserHelper::editUser( + $this->getBaseUrl(), + $this->getActualUsername($targetUser), + 'quota', + $quota, + $this->getActualUsername($requestingUser), + $this->getPasswordForUser($requestingUser), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @When /^user "([^"]*)" changes the quota of user "([^"]*)" to "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $quota + * + * @return void + */ + public function userChangesTheQuotaOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $quota + ):void { + $this->userChangeQuotaOfUserUsingProvisioningApi( + $requestingUser, + $targetUser, + $quota + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has changed the quota of user "([^"]*)" to "([^"]*)"$/ + * + * @param string $requestingUser + * @param string $targetUser + * @param string $quota + * + * @return void + */ + public function userHasChangedTheQuotaOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser, + string $quota + ):void { + $this->userChangeQuotaOfUserUsingProvisioningApi( + $requestingUser, + $targetUser, + $quota + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * + * @return void + * @throws JsonException + */ + public function retrieveUserInformationAsAdminUsingProvisioningApi( + string $user + ):void { + $result = UserHelper::getUser( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @When /^the administrator retrieves the information of user "([^"]*)" using the provisioning API$/ + * + * @param string $user + * + * @return void + * @throws JsonException + */ + public function adminRetrievesTheInformationOfUserUsingTheProvisioningApi( + string $user + ):void { + $user = $this->getActualUsername($user); + $this->retrieveUserInformationAsAdminUsingProvisioningApi( + $user + ); + } + + /** + * @Given /^the administrator has retrieved the information of user "([^"]*)"$/ + * + * @param string $user + * + * @return void + * @throws JsonException + */ + public function adminHasRetrievedTheInformationOfUserUsingTheProvisioningApi( + string $user + ):void { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminHasRetrievedUserUsingTheGraphApi($user); + } else { + $this->retrieveUserInformationAsAdminUsingProvisioningApi($user); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + } + + /** + * @param string $requestingUser + * @param string $targetUser + * + * @return void + * @throws JsonException + */ + public function userRetrieveUserInformationUsingProvisioningApi( + string $requestingUser, + string $targetUser + ):void { + $result = UserHelper::getUser( + $this->getBaseUrl(), + $this->getActualUsername($targetUser), + $this->getActualUsername($requestingUser), + $this->getPasswordForUser($requestingUser), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @When /^user "([^"]*)" retrieves the information of user "([^"]*)" using the provisioning API$/ + * + * @param string $requestingUser + * @param string $targetUser + * + * @return void + * @throws JsonException + */ + public function userRetrievesTheInformationOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser + ):void { + $this->userRetrieveUserInformationUsingProvisioningApi( + $requestingUser, + $targetUser + ); + } + + /** + * @Given /^user "([^"]*)" has retrieved the information of user "([^"]*)"$/ + * + * @param string $requestingUser + * @param string $targetUser + * + * @return void + * @throws JsonException + */ + public function userHasRetrievedTheInformationOfUserUsingTheProvisioningApi( + string $requestingUser, + string $targetUser + ):void { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userHasRetrievedUserUsingTheGraphApi( + $requestingUser, + $targetUser + ); + } else { + $this->userRetrieveUserInformationUsingProvisioningApi( + $requestingUser, + $targetUser + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + } + + /** + * @Then /^user "([^"]*)" should exist$/ + * + * @param string $user + * + * @return void + * @throws JsonException + */ + public function userShouldExist(string $user):void { + $user = $this->getActualUsername($user); + Assert::assertTrue( + $this->userExists($user), + "User '$user' should exist but does not exist" + ); + } + + /** + * @Then the following users should exist + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersShouldExist(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->userShouldExist($username["username"]); + } + } + + /** + * @Then /^user "([^"]*)" should not exist$/ + * + * @param string $user + * + * @return void + * @throws JsonException + */ + public function userShouldNotExist(string $user):void { + $user = $this->getActualUsername($user); + Assert::assertFalse( + $this->userExists($user), + "User '$user' should not exist but does exist" + ); + $this->rememberThatUserIsNotExpectedToExist($user); + } + + /** + * @Then the following users should not exist + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersShouldNotExist(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->userShouldNotExist($username["username"]); + } + } + + /** + * @Then /^group "([^"]*)" should exist$/ + * + * @param string $group + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function groupShouldExist(string $group):void { + Assert::assertTrue( + $this->groupExists($group), + "Group '$group' should exist but does not exist" + ); + } + + /** + * @Then /^group "([^"]*)" should not exist$/ + * + * @param string $group + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function groupShouldNotExist(string $group):void { + Assert::assertFalse( + $this->groupExists($group), + "Group '$group' should not exist but does exist" + ); + } + + /** + * @Then the following groups should not exist + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingGroupsShouldNotExist(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["groupname"]); + $groups = $table->getHash(); + foreach ($groups as $group) { + $this->groupShouldNotExist($group["groupname"]); + } + } + + /** + * @Then /^these groups should (not|)\s?exist:$/ + * expects a table of groups with the heading "groupname" + * + * @param string $shouldOrNot (not|) + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theseGroupsShouldNotExist(string $shouldOrNot, TableNode $table):void { + $should = ($shouldOrNot !== "not"); + $this->verifyTableNodeColumns($table, ['groupname']); + $useGraph = OcisHelper::isTestingWithGraphApi(); + if ($useGraph) { + $this->graphContext->theseGroupsShouldNotExist($shouldOrNot, $table); + } else { + $groups = $this->getArrayOfGroupsResponded($this->getAllGroups()); + foreach ($table as $row) { + if (\in_array($row['groupname'], $groups, true) !== $should) { + throw new Exception( + "group '" . $row['groupname'] . + "' does" . ($should ? " not" : "") . + " exist but should" . ($should ? "" : " not") + ); + } + } + } + } + + /** + * @Given /^user "([^"]*)" has been deleted$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userHasBeenDeleted(string $user):void { + $user = $this->getActualUsername($user); + if ($this->userExists($user)) { + if ($this->isTestingWithLdap() && \in_array($user, $this->ldapCreatedUsers)) { + $this->deleteLdapUser($user); + } else { + $this->deleteTheUserUsingTheProvisioningApi($user); + } + } + $this->userShouldNotExist($user); + } + + /** + * @Given the following users have been deleted + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersHaveBeenDeleted(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->userHasBeenDeleted($username["username"]); + } + } + + /** + * @Given these users have been initialized: + * expects a table of users with the heading + * "|username|password|" + * + * @param TableNode $table + * + * @return void + */ + public function theseUsersHaveBeenInitialized(TableNode $table):void { + foreach ($table as $row) { + if (!isset($row ['password'])) { + $password = $this->getPasswordForUser($row ['username']); + } else { + $password = $row ['password']; + } + $this->initializeUser( + $row ['username'], + $password + ); + } + } + + /** + * @When the administrator gets all the members of group :group using the provisioning API + * + * @param string $group + * + * @return void + */ + public function theAdministratorGetsAllTheMembersOfGroupUsingTheProvisioningApi(string $group):void { + $this->userGetsAllTheMembersOfGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $group + ); + } + + /** + * @When /^user "([^"]*)" gets all the members of group "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $group + * + * @return void + */ + public function userGetsAllTheMembersOfGroupUsingTheProvisioningApi(string $user, string $group):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v{$this->ocsApiVersion}.php/cloud/groups/$group"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getActualUsername($user), + $this->getPasswordForUser($user) + ); + } + + /** + * get all the existing groups + * + * @return ResponseInterface + */ + public function getAllGroups():ResponseInterface { + $fullUrl = $this->getBaseUrl() . "/ocs/v{$this->ocsApiVersion}.php/cloud/groups"; + return HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + } + + /** + * @When the administrator gets all the groups using the provisioning API + * + * @return void + */ + public function theAdministratorGetsAllTheGroupsUsingTheProvisioningApi():void { + $this->response = $this->getAllGroups(); + } + + /** + * @When /^user "([^"]*)" tries to get all the groups using the provisioning API$/ + * @When /^user "([^"]*)" gets all the groups using the provisioning API$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userTriesToGetAllTheGroupsUsingTheProvisioningApi(string $user):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v{$this->ocsApiVersion}.php/cloud/groups"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + } + + /** + * @When the administrator gets all the groups of user :user using the provisioning API + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theAdministratorGetsAllTheGroupsOfUser(string $user):void { + $this->userGetsAllTheGroupsOfUser($this->getAdminUsername(), $user); + } + + /** + * @When user :user gets all the groups of user :otherUser using the provisioning API + * + * @param string $user + * @param string $otherUser + * + * @return void + * @throws Exception + */ + public function userGetsAllTheGroupsOfUser(string $user, string $otherUser):void { + $actualOtherUser = $this->getActualUsername($otherUser); + $fullUrl = $this->getBaseUrl() . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$actualOtherUser/groups"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + } + + /** + * @When the administrator gets the list of all users using the provisioning API + * + * @return void + */ + public function theAdministratorGetsTheListOfAllUsersUsingTheProvisioningApi():void { + $this->userGetsTheListOfAllUsersUsingTheProvisioningApi($this->getAdminUsername()); + } + + /** + * @When user :user gets the list of all users using the provisioning API + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userGetsTheListOfAllUsersUsingTheProvisioningApi(string $user):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v{$this->ocsApiVersion}.php/cloud/users"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + } + + /** + * Make a request about the user. That will force the server to fully + * initialize the user, including their skeleton files. + * + * @param string $user + * @param string $password + * + * @return void + */ + public function initializeUser(string $user, string $password):void { + $url = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$user"; + HttpRequestHelper::get( + $url, + $this->getStepLineRef(), + $user, + $password + ); + $this->lastUploadTime = \time(); + } + + /** + * Touch an API end-point for each user so that their file-system gets setup + * + * @param array $users + * + * @return void + * @throws Exception + */ + public function initializeUsers(array $users):void { + $url = "/cloud/users/%s"; + foreach ($users as $user) { + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + 'GET', + \sprintf($url, $user), + $this->getStepLineRef() + ); + $this->setResponse($response); + $this->theHTTPStatusCodeShouldBe(200); + } + } + + /** + * adds a user to the list of users that were created during test runs + * makes it possible to use this list in other test steps + * or to delete them at the end of the test + * + * @param string|null $user + * @param string|null $password + * @param string|null $displayName + * @param string|null $email + * @param string|null $userId only set for the users created using the Graph API + * @param bool $shouldExist + * + * @return void + * @throws JsonException + */ + public function addUserToCreatedUsersList( + ?string $user, + ?string $password, + ?string $displayName = null, + ?string $email = null, + ?string $userId = null, + bool $shouldExist = true + ):void { + $user = $this->getActualUsername($user); + $normalizedUsername = $this->normalizeUsername($user); + $userData = [ + "password" => $password, + "displayname" => $displayName, + "email" => $email, + "shouldExist" => $shouldExist, + "actualUsername" => $user, + "id" => $userId + ]; + + if ($this->currentServer === 'LOCAL') { + // Only remember this user creation if it was expected to have been successful + // or the user has not been processed before. Some tests create a user the + // first time (successfully) and then purposely try to create the user again. + // The 2nd user creation is expected to fail, and in that case we want to + // still remember the details of the first user creation. + if ($shouldExist || !\array_key_exists($normalizedUsername, $this->createdUsers)) { + $this->createdUsers[$normalizedUsername] = $userData; + } + } elseif ($this->currentServer === 'REMOTE') { + // See comment above about the LOCAL case. The logic is the same for the remote case. + if ($shouldExist || !\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + $this->createdRemoteUsers[$normalizedUsername] = $userData; + } + } + } + + /** + * remember the password of a user that already exists so that you can use + * ordinary test steps after changing their password. + * + * @param string $user + * @param string $password + * + * @return void + */ + public function rememberUserPassword( + string $user, + string $password + ):void { + $normalizedUsername = $this->normalizeUsername($user); + if ($this->currentServer === 'LOCAL') { + if (\array_key_exists($normalizedUsername, $this->createdUsers)) { + $this->createdUsers[$normalizedUsername]['password'] = $password; + } + } elseif ($this->currentServer === 'REMOTE') { + if (\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { + $this->createdRemoteUsers[$user]['password'] = $password; + } + } + } + + /** + * Remembers that a user from the list of users that were created during + * test runs is no longer expected to exist. Useful if a user was created + * during the setup phase but was deleted in a test run. We don't expect + * this user to exist in the tear-down phase, so remember that fact. + * + * @param string $user + * + * @return void + */ + public function rememberThatUserIsNotExpectedToExist(string $user):void { + $user = $this->getActualUsername($user); + $normalizedUsername = $this->normalizeUsername($user); + if (\array_key_exists($normalizedUsername, $this->createdUsers)) { + $this->createdUsers[$normalizedUsername]['shouldExist'] = false; + } + } + + /** + * creates a single user + * + * @param string|null $user + * @param string|null $password if null, then select a password + * @param string|null $displayName + * @param string|null $email + * @param bool $initialize initialize the user skeleton files etc + * @param string|null $method how to create the user api|occ, default api + * @param bool $setDefault sets the missing values to some default + * @param bool $skeleton + * + * @return void + * @throws Exception + */ + public function createUser( + ?string $user, + ?string $password = null, + ?string $displayName = null, + ?string $email = null, + bool $initialize = true, + ?string $method = null, + bool $setDefault = true, + bool $skeleton = true + ):void { + $userId = null; + if ($password === null) { + $password = $this->getPasswordForUser($user); + } + + if ($displayName === null && $setDefault === true) { + $displayName = $this->getDisplayNameForUser($user); + if ($displayName === null) { + $displayName = $this->getDisplayNameForUser('regularuser'); + } + } + + if ($email === null && $setDefault === true) { + $email = $this->getEmailAddressForUser($user); + + if ($email === null) { + // escape @ & space if present in userId + $email = \str_replace(["@", " "], "", $user) . '@owncloud.com'; + } + } + + $user = $this->getActualUsername($user); + + if ($method === null && $this->isTestingWithLdap()) { + //guess yourself + $method = "ldap"; + } elseif (OcisHelper::isTestingWithGraphApi()) { + $method = "graph"; + } elseif ($method === null) { + $method = "api"; + } + $user = \trim($user); + $method = \trim(\strtolower($method)); + switch ($method) { + case "occ": + $result = SetupHelper::createUser( + $user, + $password, + $this->getStepLineRef(), + $displayName, + $email + ); + if ($result["code"] !== "0") { + throw new Exception( + __METHOD__ . " could not create user. {$result['stdOut']} {$result['stdErr']}" + ); + } + break; + case "api": + case "ldap": + $settings = []; + $setting["userid"] = $user; + $setting["displayName"] = $displayName; + $setting["password"] = $password; + $setting["email"] = $email; + \array_push($settings, $setting); + try { + $this->usersHaveBeenCreated( + $initialize, + $settings, + $method, + $skeleton + ); + } catch (LdapException $exception) { + throw new Exception( + __METHOD__ . " cannot create a LDAP user with provided data. Error: {$exception}" + ); + } + break; + case "graph": + $this->graphContext->theAdminHasCreatedUser( + $user, + $password, + $email, + $displayName, + ); + $newUser = $this->getJsonDecodedResponse(); + $userId = $newUser['id']; + break; + default: + throw new InvalidArgumentException( + __METHOD__ . " Invalid method to create a user" + ); + } + + $this->addUserToCreatedUsersList($user, $password, $displayName, $email, $userId); + if ($initialize) { + $this->initializeUser($user, $password); + } + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function deleteUser(string $user):void { + $this->deleteTheUserUsingTheProvisioningApi($user); + $this->userShouldNotExist($user); + } + + /** + * Try to delete the group, catching anything bad that might happen. + * Use this method only in places where you want to try as best you + * can to delete the group, but do not want to error if there is a problem. + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function cleanupGroup(string $group):void { + try { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminHasDeletedGroupUsingTheGraphApi($group); + } else { + $this->deleteTheGroupUsingTheProvisioningApi($group); + } + } catch (Exception $e) { + \error_log( + "INFORMATION: There was an unexpected problem trying to delete group " . + "'$group' message '" . $e->getMessage() . "'" + ); + } + + if ($this->theGroupShouldBeAbleToBeDeleted($group) + && $this->groupExists($group) + ) { + \error_log( + "INFORMATION: tried to delete group '$group'" . + " at the end of the scenario but it seems to still exist. " . + "There might be problems with later scenarios." + ); + } + } + + /** + * @param string|null $user + * + * @return bool + * @throws JsonException + */ + public function userExists(?string $user):bool { + // in OCIS there is no admin user and in oC10 there are issues when + // sending the username in lowercase in the auth but in uppercase in + // the URL see https://github.com/owncloud/core/issues/36822 + $user = $this->getActualUsername($user); + if (OcisHelper::isTestingOnOcisOrReva()) { + // In OCIS an intermittent issue restricts users to list their own account + // So use admin account to list the user + // https://github.com/owncloud/ocis/issues/820 + // The special code can be reverted once the issue is fixed + if (OcisHelper::isTestingParallelDeployment()) { + $requestingUser = $this->getActualUsername($user); + $requestingPassword = $this->getPasswordForUser($user); + } elseif (OcisHelper::isTestingWithGraphApi()) { + $requestingUser = $this->getAdminUsername(); + $requestingPassword = $this->getAdminPassword(); + } elseif (OcisHelper::isTestingOnOcis()) { + $requestingUser = 'moss'; + $requestingPassword = 'vista'; + } else { + $requestingUser = $this->getActualUsername($user); + $requestingPassword = $this->getPasswordForUser($requestingUser); + } + } else { + $requestingUser = $this->getAdminUsername(); + $requestingPassword = $this->getAdminPassword(); + } + $path = (OcisHelper::isTestingWithGraphApi()) + ? "/graph/v1.0" + : "/ocs/v2.php/cloud"; + $fullUrl = $this->getBaseUrl() . $path . "/users/$user"; + + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $requestingUser, + $requestingPassword + ); + if ($this->response->getStatusCode() >= 400) { + return false; + } + return true; + } + + /** + * @Then /^user "([^"]*)" should belong to group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userShouldBelongToGroup(string $user, string $group):void { + $user = $this->getActualUsername($user); + $respondedArray = []; + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userShouldBeMemberInGroupUsingTheGraphApi( + $user, + $group + ); + } else { + $this->theAdministratorGetsAllTheGroupsOfUser($user); + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + \sort($respondedArray); + Assert::assertContains( + $group, + $respondedArray, + __METHOD__ . " Group '$group' does not exist in '" + . \implode(', ', $respondedArray) + . "'" + ); + Assert::assertEquals( + 200, + $this->response->getStatusCode(), + __METHOD__ + . " Expected status code is '200' but got '" + . $this->response->getStatusCode() + . "'" + ); + } + } + + /** + * @Then the following users should belong to the following groups + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theTheFollowingUserShouldBelongToTheFollowingGroup(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username", "groupname"]); + $rows = $table->getHash(); + foreach ($rows as $row) { + $this->userShouldBelongToGroup($row["username"], $row["groupname"]); + } + } + + /** + * @param string $group + * + * @return array + */ + public function getUsersOfLdapGroup(string $group):array { + $ou = $this->getLdapGroupsOU(); + $entry = 'cn=' . $group . ',ou=' . $ou . ',' . $this->ldapBaseDN; + $ldapResponse = $this->ldap->getEntry($entry); + return $ldapResponse["memberuid"]; + } + + /** + * @Then /^user "([^"]*)" should not belong to group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws JsonException + */ + public function userShouldNotBelongToGroup(string $user, string $group):void { + $user = $this->getActualUsername($user); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->userShouldNotBeMemberInGroupUsingTheGraphApi($user, $group); + } else { + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/users/$user/groups"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + \sort($respondedArray); + Assert::assertNotContains($group, $respondedArray); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + } + } + + /** + * @Then the following users should not belong to the following groups + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theTheFollowingUserShouldNotBelongToTheFollowingGroup(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username", "groupname"]); + $rows = $table->getHash(); + foreach ($rows as $row) { + $this->userShouldNotBelongToGroup($row["username"], $row["groupname"]); + } + } + + /** + * @Then group :group should not contain user :username + * + * @param string $group + * @param string $username + * + * @return void + */ + public function groupShouldNotContainUser(string $group, string $username):void { + $username = $this->getActualUsername($username); + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/groups/$group"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $this->theUsersReturnedByTheApiShouldNotInclude($username); + } + + /** + * @param string $user + * @param string $group + * + * @return bool + */ + public function userBelongsToGroup(string $user, string $group):bool { + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/users/$user/groups"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + + if (\in_array($group, $respondedArray)) { + return true; + } else { + return false; + } + } + + /** + * @When /^the administrator adds user "([^"]*)" to group "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function adminAddsUserToGroupUsingTheProvisioningApi(string $user, string $group):void { + $this->addUserToGroup($user, $group, "api"); + } + + /** + * @When the administrator adds the following users to the following groups using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorAddsUserToTheFollowingGroupsUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username", "groupname"], ["comment"]); + $rows = $table->getHash(); + foreach ($rows as $row) { + $this->adminAddsUserToGroupUsingTheProvisioningApi($row["username"], $row["groupname"]); + } + } + + /** + * @When user :user tries to add user :otherUser to group :group using the provisioning API + * + * @param string $user + * @param string $otherUser + * @param string $group + * + * @return void + * @throws Exception + */ + public function userTriesToAddUserToGroupUsingTheProvisioningApi(string $user, string $otherUser, string $group):void { + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $actualOtherUser = $this->getActualUsername($otherUser); + $result = UserHelper::addUserToGroup( + $this->getBaseUrl(), + $actualOtherUser, + $group, + $actualUser, + $actualPassword, + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->response = $result; + } + + /** + * @When user :user tries to add himself to group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userTriesToAddHimselfToGroupUsingTheProvisioningApi(string $user, string $group):void { + $this->userTriesToAddUserToGroupUsingTheProvisioningApi($user, $user, $group); + } + + /** + * @When the administrator tries to add user :user to group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function theAdministratorTriesToAddUserToGroupUsingTheProvisioningApi( + string $user, + string $group + ):void { + $this->userTriesToAddUserToGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $user, + $group + ); + } + + /** + * @Given /^user "([^"]*)" has been added to group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userHasBeenAddedToGroup(string $user, string $group):void { + $user = $this->getActualUsername($user); + $this->addUserToGroup($user, $group, null, true); + } + + /** + * @Given the following users have been added to the following groups + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUserHaveBeenAddedToTheFollowingGroup(TableNode $table):void { + $this->verifyTableNodeColumns($table, ['username', 'groupname']); + foreach ($table as $row) { + $this->userHasBeenAddedToGroup($row['username'], $row['groupname']); + } + } + + /** + * @Given /^user "([^"]*)" has been added to database backend group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userHasBeenAddedToDatabaseBackendGroup(string $user, string $group):void { + $this->addUserToGroup($user, $group, 'api', true); + } + + /** + * @param string $user + * @param string $group + * @param string|null $method how to add the user to the group api|occ + * @param bool $checkResult if true, then check the status of the operation. default false. + * for given step checkResult is expected to be set as true + * for when step checkResult is expected to be set as false + * + * @return void + * @throws Exception + */ + public function addUserToGroup(string $user, string $group, ?string $method = null, bool $checkResult = false):void { + $user = $this->getActualUsername($user); + if ($method === null + && $this->isTestingWithLdap() + && !$this->isLocalAdminGroup($group) + ) { + //guess yourself + $method = "ldap"; + } elseif ($method === null && OcisHelper::isTestingWithGraphApi()) { + $method = "graph"; + } elseif ($method === null) { + $method = "api"; + } + $method = \trim(\strtolower($method)); + switch ($method) { + case "api": + $result = UserHelper::addUserToGroup( + $this->getBaseUrl(), + $user, + $group, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + if ($checkResult && ($result->getStatusCode() !== 200)) { + throw new Exception( + "could not add user to group. " + . $result->getStatusCode() . " " . $result->getBody() + ); + } + $this->response = $result; + if (!$checkResult) { + // for when step only + $this->pushToLastStatusCodesArrays(); + } + break; + case "occ": + $result = SetupHelper::addUserToGroup( + $group, + $user, + $this->getStepLineRef() + ); + if ($checkResult && ($result["code"] !== "0")) { + throw new Exception( + "could not add user to group. {$result['stdOut']} {$result['stdErr']}" + ); + } + break; + case "ldap": + try { + $this->addUserToLdapGroup( + $user, + $group + ); + } catch (LdapException $exception) { + throw new Exception( + "User " . $user . " cannot be added to " . $group . " . Error: {$exception}" + ); + }; + break; + case "graph": + $this->graphContext->adminHasAddedUserToGroupUsingTheGraphApi( + $user, + $group + ); + break; + default: + throw new InvalidArgumentException( + "Invalid method to add a user to a group" + ); + } + } + + /** + * @Given the administrator has been added to group :group + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function theAdministratorHasBeenAddedToGroup(string $group):void { + $admin = $this->getAdminUsername(); + $this->addUserToGroup($admin, $group, null, true); + } + + /** + * @param string $group + * @param bool $shouldExist - true if the group should exist + * @param bool $possibleToDelete - true if it is possible to delete the group + * @param string|null $id - id of the group, only required for the groups created using the Graph API + * + * @return void + */ + public function addGroupToCreatedGroupsList( + string $group, + bool $shouldExist = true, + bool $possibleToDelete = true, + ?string $id = null + ):void { + $groupData = [ + "shouldExist" => $shouldExist, + "possibleToDelete" => $possibleToDelete + ]; + if ($id !== null) { + $groupData["id"] = $id; + } + + if ($this->currentServer === 'LOCAL') { + $this->createdGroups[$group] = $groupData; + } elseif ($this->currentServer === 'REMOTE') { + $this->createdRemoteGroups[$group] = $groupData; + } + } + + /** + * Remembers that a group from the list of groups that were created during + * test runs is no longer expected to exist. Useful if a group was created + * during the setup phase but was deleted in a test run. We don't expect + * this group to exist in the tear-down phase, so remember that fact. + * + * @param string $group + * + * @return void + */ + public function rememberThatGroupIsNotExpectedToExist(string $group):void { + if (\array_key_exists($group, $this->createdGroups)) { + $this->createdGroups[$group]['shouldExist'] = false; + } + } + + /** + * @When /^the administrator creates group "([^"]*)" using the provisioning API$/ + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function adminCreatesGroupUsingTheProvisioningApi(string $group):void { + if (!$this->groupExists($group)) { + $this->createTheGroup($group, 'api'); + } + $this->groupShouldExist($group); + } + + /** + * @Given /^group "([^"]*)" has been created$/ + * + * @param string $group + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function groupHasBeenCreated(string $group):void { + $this->createTheGroup($group); + $this->groupShouldExist($group); + } + + /** + * @Given /^group "([^"]*)" has been created in the database user backend$/ + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function groupHasBeenCreatedOnDatabaseBackend(string $group):void { + $this->adminCreatesGroupUsingTheProvisioningApi($group); + } + + /** + * @Given these groups have been created: + * expects a table of groups with the heading "groupname" + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theseGroupsHaveBeenCreated(TableNode $table):void { + $this->verifyTableNodeColumns($table, ['groupname'], ['comment']); + foreach ($table as $row) { + $this->createTheGroup($row['groupname']); + } + } + + /** + * @When /^the administrator sends a group creation request for group "([^"]*)" using the provisioning API$/ + * + * @param string $group + * @param string|null $user + * + * @return void + */ + public function adminSendsGroupCreationRequestUsingTheProvisioningApi( + string $group, + ?string $user = null + ):void { + $bodyTable = new TableNode([['groupid', $group]]); + $user = $user === null ? $this->getAdminUsername() : $user; + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "POST", + "/cloud/groups", + $bodyTable + ); + $this->pushToLastStatusCodesArrays(); + $this->addGroupToCreatedGroupsList($group); + } + + /** + * @When the administrator sends a group creation request for the following groups using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorSendsAGroupCreationRequestForTheFollowingGroupUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["groupname"], ["comment"]); + $groups = $table->getHash(); + foreach ($groups as $group) { + $this->adminSendsGroupCreationRequestUsingTheProvisioningApi($group["groupname"]); + } + } + + /** + * @When /^the administrator tries to send a group creation request for group "([^"]*)" using the provisioning API$/ + * + * @param string $group + * + * @return void + */ + public function adminTriesToSendGroupCreationRequestUsingTheAPI(string $group):void { + $this->adminSendsGroupCreationRequestUsingTheProvisioningApi($group); + $this->rememberThatGroupIsNotExpectedToExist($group); + } + + /** + * @When /^user "([^"]*)" tries to send a group creation request for group "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $group + * + * @return void + */ + public function userTriesToSendGroupCreationRequestUsingTheAPI(string $user, string $group):void { + $this->adminSendsGroupCreationRequestUsingTheProvisioningApi($group, $user); + $this->rememberThatGroupIsNotExpectedToExist($group); + } + + /** + * creates a single group + * + * @param string $group + * @param string|null $method how to create the group api|occ + * + * @return void + * @throws Exception + */ + public function createTheGroup(string $group, ?string $method = null):void { + //guess yourself + if ($method === null) { + if ($this->isTestingWithLdap()) { + $method = "ldap"; + } elseif (OcisHelper::isTestingWithGraphApi()) { + $method = "graph"; + } else { + $method = "api"; + } + } + $group = \trim($group); + $method = \trim(\strtolower($method)); + $groupCanBeDeleted = false; + $groupId = null; + switch ($method) { + case "api": + $result = UserHelper::createGroup( + $this->getBaseUrl(), + $group, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef() + ); + if ($result->getStatusCode() === 200) { + $groupCanBeDeleted = true; + } else { + throw new Exception( + "could not create group '$group'. " + . $result->getStatusCode() . " " . $result->getBody() + ); + } + break; + case "occ": + $result = SetupHelper::createGroup( + $group, + $this->getStepLineRef() + ); + if ($result["code"] == 0) { + $groupCanBeDeleted = true; + } else { + throw new Exception( + "could not create group '$group'. {$result['stdOut']} {$result['stdErr']}" + ); + } + break; + case "ldap": + try { + $this->createLdapGroup($group); + } catch (LdapException $e) { + throw new Exception( + "could not create group '$group'. Error: {$e}" + ); + } + break; + case "graph": + $newGroup = $this->graphContext->adminHasCreatedGroupUsingTheGraphApi($group); + $groupCanBeDeleted = true; + $groupId = $newGroup["id"]; + break; + default: + throw new InvalidArgumentException( + "Invalid method to create group '$group'" + ); + } + + $this->addGroupToCreatedGroupsList($group, true, $groupCanBeDeleted, $groupId); + } + + /** + * @param string $attribute + * @param string $entry + * @param string $value + * @param bool $append + * + * @return void + * @throws Exception + */ + public function setTheLdapAttributeOfTheEntryTo( + string $attribute, + string $entry, + string $value, + bool $append = false + ):void { + $ldapEntry = $this->ldap->getEntry($entry . "," . $this->ldapBaseDN); + Laminas\Ldap\Attribute::setAttribute($ldapEntry, $attribute, $value, $append); + $this->ldap->update($entry . "," . $this->ldapBaseDN, $ldapEntry); + $this->theLdapUsersHaveBeenReSynced(); + } + + /** + * @param string $user + * @param string $group + * @param string|null $ou + * + * @return void + * @throws Exception + */ + public function addUserToLdapGroup(string $user, string $group, ?string $ou = null):void { + if ($ou === null) { + $ou = $this->getLdapGroupsOU(); + } + $memberAttr = ""; + $memberValue = ""; + if ($this->ldapGroupSchema == "rfc2307") { + $memberAttr = "memberUID"; + $memberValue = "$user"; + } else { + $memberAttr = "member"; + $userbase = "ou=" . $this->getLdapUsersOU() . "," . $this->ldapBaseDN; + $memberValue = "uid=$user" . "," . "$userbase"; + } + $this->setTheLdapAttributeOfTheEntryTo( + $memberAttr, + "cn=$group,ou=$ou", + $memberValue, + true + ); + } + + /** + * @param string $value + * @param string $attribute + * @param string $entry + * + * @return void + */ + public function deleteValueFromLdapAttribute(string $value, string $attribute, string $entry):void { + $this->ldap->deleteAttributes( + $entry . "," . $this->ldapBaseDN, + [$attribute => [$value]] + ); + } + + /** + * @param string $user + * @param string $group + * @param string|null $ou + * + * @return void + * @throws Exception + */ + public function removeUserFromLdapGroup(string $user, string $group, ?string $ou = null):void { + if ($ou === null) { + $ou = $this->getLdapGroupsOU(); + } + $memberAttr = ""; + $memberValue = ""; + if ($this->ldapGroupSchema == "rfc2307") { + $memberAttr = "memberUID"; + $memberValue = "$user"; + } else { + $memberAttr = "member"; + $userbase = "ou=" . $this->getLdapUsersOU() . "," . $this->ldapBaseDN; + $memberValue = "uid=$user" . "," . "$userbase"; + } + $this->deleteValueFromLdapAttribute( + $memberValue, + $memberAttr, + "cn=$group,ou=$ou" + ); + $this->theLdapUsersHaveBeenReSynced(); + } + + /** + * @param string $entry + * + * @return void + * @throws Exception + */ + public function deleteTheLdapEntry(string $entry):void { + $this->ldap->delete($entry . "," . $this->ldapBaseDN); + $this->theLdapUsersHaveBeenReSynced(); + } + + /** + * @param string $group + * @param string|null $ou + * + * @return void + * @throws LdapException + * @throws Exception + */ + public function deleteLdapGroup(string $group, ?string $ou = null):void { + if ($ou === null) { + $ou = $this->getLdapGroupsOU(); + } + $this->deleteTheLdapEntry("cn=$group,ou=$ou"); + $this->theLdapUsersHaveBeenReSynced(); + $key = \array_search($group, $this->ldapCreatedGroups); + if ($key !== false) { + unset($this->ldapCreatedGroups[$key]); + } + $this->rememberThatGroupIsNotExpectedToExist($group); + } + + /** + * @param string|null $username + * @param string|null $ou + * + * @return void + * @throws Exception + */ + public function deleteLdapUser(?string $username, ?string $ou = null):void { + if (!\in_array($username, $this->ldapCreatedUsers)) { + throw new Error( + "User " . $username . " was not created using Ldap and does not exist as an Ldap User" + ); + } + if ($ou === null) { + $ou = $this->getLdapUsersOU(); + } + $entry = "uid=$username,ou=$ou"; + $this->deleteTheLdapEntry($entry); + $key = \array_search($username, $this->ldapCreatedUsers); + if ($key !== false) { + unset($this->ldapCreatedUsers[$key]); + } + $this->rememberThatUserIsNotExpectedToExist($username); + } + + /** + * @param string|null $user + * @param string|null $displayName + * + * @return void + * @throws Exception + */ + public function editLdapUserDisplayName(?string $user, ?string $displayName):void { + $entry = "uid=" . $user . ",ou=" . $this->getLdapUsersOU(); + $this->setTheLdapAttributeOfTheEntryTo( + 'displayname', + $entry, + $displayName + ); + $this->theLdapUsersHaveBeenReSynced(); + } + + /** + * @When /^the administrator disables user "([^"]*)" using the provisioning API$/ + * + * @param string|null $user + * + * @return void + */ + public function adminDisablesUserUsingTheProvisioningApi(?string $user):void { + $user = $this->getActualUsername($user); + $this->disableOrEnableUser($this->getAdminUsername(), $user, 'disable'); + } + + /** + * @When the administrator disables the following users using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorDisablesTheFollowingUsersUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->adminDisablesUserUsingTheProvisioningApi($username["username"]); + } + } + + /** + * @Given /^user "([^"]*)" has been disabled$/ + * + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function adminHasDisabledUserUsingTheProvisioningApi(?string $user):void { + $user = $this->getActualUsername($user); + $this->disableOrEnableUser($this->getAdminUsername(), $user, 'disable'); + $this->theHTTPStatusCodeShouldBeSuccess(); + $this->ocsContext->assertOCSResponseIndicatesSuccess(); + } + + /** + * @Given the following users have been disabled + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersHaveBeenDisabled(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->adminHasDisabledUserUsingTheProvisioningApi($username["username"]); + } + } + + /** + * @When user :user disables user :otherUser using the provisioning API + * + * @param string $user + * @param string $otherUser + * + * @return void + */ + public function userDisablesUserUsingTheProvisioningApi(string $user, string $otherUser):void { + $user = $this->getActualUsername($user); + $actualOtherUser = $this->getActualUsername($otherUser); + $this->disableOrEnableUser($user, $actualOtherUser, 'disable'); + } + + /** + * @When the administrator enables user :user using the provisioning API + * + * @param string $user + * + * @return void + */ + public function theAdministratorEnablesUserUsingTheProvisioningApi(string $user):void { + $this->disableOrEnableUser($this->getAdminUsername(), $user, 'enable'); + } + + /** + * @When the administrator enables the following users using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorEnablesTheFollowingUsersUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->theAdministratorEnablesUserUsingTheProvisioningApi($username["username"]); + } + } + + /** + * @When /^user "([^"]*)" enables user "([^"]*)" using the provisioning API$/ + * @When /^user "([^"]*)" tries to enable user "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $otherUser + * + * @return void + */ + public function userTriesToEnableUserUsingTheProvisioningApi( + string $user, + string $otherUser + ):void { + $this->disableOrEnableUser($user, $otherUser, 'enable'); + } + + /** + * @param string $user + * + * @return void + * @throws Exception + */ + public function deleteTheUserUsingTheProvisioningApi(string $user):void { + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + // Always try to delete the user + if (OcisHelper::isTestingWithGraphApi()) { + // users can be deleted using the username in the GraphApi too + $this->graphContext->adminDeletesUserUsingTheGraphApi($user); + } else { + $this->response = UserHelper::deleteUser( + $this->getBaseUrl(), + $user, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + } + $this->pushToLastStatusCodesArrays(); + + // Only log a message if the test really expected the user to have been + // successfully created (i.e. the delete is expected to work) and + // there was a problem deleting the user. Because in this case there + // might be an effect on later tests. + if ($this->theUserShouldExist($user) + && (!\in_array($this->response->getStatusCode(), [200, 204])) + ) { + \error_log( + "INFORMATION: could not delete user '$user' " + . $this->response->getStatusCode() . " " . $this->response->getBody() + ); + } + + $this->rememberThatUserIsNotExpectedToExist($user); + } + + /** + * @param string $group group name + * + * @return void + * @throws Exception + * @throws LdapException + */ + public function deleteGroup(string $group):void { + if ($this->groupExists($group)) { + if ($this->isTestingWithLdap() && \in_array($group, $this->ldapCreatedGroups)) { + $this->deleteLdapGroup($group); + } else { + $this->deleteTheGroupUsingTheProvisioningApi($group); + } + } + } + + /** + * @Given /^group "([^"]*)" has been deleted$/ + * + * @param string $group + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function groupHasBeenDeleted(string $group):void { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminHasDeletedGroupUsingTheGraphApi($group); + } else { + $this->deleteGroup($group); + } + $this->groupShouldNotExist($group); + } + + /** + * @When /^the administrator deletes group "([^"]*)" from the default user backend$/ + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function adminDeletesGroup(string $group):void { + $this->deleteGroup($group); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^the administrator deletes group "([^"]*)" using the provisioning API$/ + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function deleteTheGroupUsingTheProvisioningApi(string $group):void { + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + $this->response = UserHelper::deleteGroup( + $this->getBaseUrl(), + $group, + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + $this->pushToLastStatusCodesArrays(); + if ($this->theGroupShouldExist($group) + && $this->theGroupShouldBeAbleToBeDeleted($group) + && ($this->response->getStatusCode() !== 200) + ) { + \error_log( + "INFORMATION: could not delete group '$group'" + . $this->response->getStatusCode() . " " . $this->response->getBody() + ); + } + + $this->rememberThatGroupIsNotExpectedToExist($group); + } + + /** + * @When the administrator deletes the following groups using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorDeletesTheFollowingGroupsUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["groupname"]); + $groups = $table->getHash(); + foreach ($groups as $group) { + $this->deleteTheGroupUsingTheProvisioningApi($group["groupname"]); + } + } + + /** + * @When user :user tries to delete group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + * @throws JsonException + */ + public function userTriesToDeleteGroupUsingTheProvisioningApi(string $user, string $group):void { + $this->response = UserHelper::deleteGroup( + $this->getBaseUrl(), + $group, + $this->getActualUsername($user), + $this->getActualPassword($user), + $this->getStepLineRef(), + $this->ocsApiVersion + ); + } + + /** + * @param string $group + * + * @return bool + * @throws Exception + * @throws GuzzleException + */ + public function groupExists(string $group):bool { + if ($this->isTestingWithLdap() && OcisHelper::isTestingOnOcisOrReva()) { + $baseDN = $this->getLdapBaseDN(); + $newDN = 'cn=' . $group . ',ou=' . $this->ldapGroupsOU . ',' . $baseDN; + if ($this->ldap->getEntry($newDN) !== null) { + return true; + } + return false; + } + if (OcisHelper::isTestingWithGraphApi()) { + $base = '/graph/v1.0'; + } else { + $base = '/ocs/v2.php/cloud'; + } + $group = \rawurlencode($group); + $fullUrl = $this->getBaseUrl() . "$base/groups/$group"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + if ($this->response->getStatusCode() >= 400) { + return false; + } + return true; + } + + /** + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function removeUserFromGroupAsAdminUsingTheProvisioningApi(string $user, string $group):void { + $this->userRemovesUserFromGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $user, + $group + ); + } + + /** + * @When the administrator removes user :user from group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function adminRemovesUserFromGroupUsingTheProvisioningApi(string $user, string $group):void { + $user = $this->getActualUsername($user); + $this->removeUserFromGroupAsAdminUsingTheProvisioningApi( + $user, + $group + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When the administrator removes the following users from the following groups using the provisioning API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorRemovesTheFollowingUserFromTheFollowingGroupUsingTheProvisioningApi(TableNode $table):void { + $this->verifyTableNodeColumns($table, ['username', 'groupname']); + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + foreach ($table as $row) { + $this->adminRemovesUserFromGroupUsingTheProvisioningApi($row['username'], $row['groupname']); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @Given user :user has been removed from group :group + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function adminHasRemovedUserFromGroup(string $user, string $group):void { + if ($this->isTestingWithLdap() + && !$this->isLocalAdminGroup($group) + && \in_array($group, $this->ldapCreatedGroups) + ) { + $this->removeUserFromLdapGroup($user, $group); + } elseif (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminHasRemovedUserFromGroupUsingTheGraphApi($user, $group); + } else { + $this->removeUserFromGroupAsAdminUsingTheProvisioningApi( + $user, + $group + ); + } + $this->userShouldNotBelongToGroup($user, $group); + } + + /** + * @When user :user removes user :otherUser from group :group using the provisioning API + * + * @param string $user + * @param string $otherUser + * @param string $group + * + * @return void + * @throws Exception + */ + public function userRemovesUserFromGroupUsingTheProvisioningApi( + string $user, + string $otherUser, + string $group + ):void { + $this->userTriesToRemoveUserFromGroupUsingTheProvisioningApi( + $user, + $otherUser, + $group + ); + + if ($this->response->getStatusCode() !== 200) { + \error_log( + "INFORMATION: could not remove user '$user' from group '$group'" + . $this->response->getStatusCode() . " " . $this->response->getBody() + ); + } + } + + /** + * @When user :user tries to remove user :otherUser from group :group using the provisioning API + * + * @param string $user + * @param string $otherUser + * @param string $group + * + * @return void + * @throws Exception + */ + public function userTriesToRemoveUserFromGroupUsingTheProvisioningApi( + string $user, + string $otherUser, + string $group + ):void { + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $actualOtherUser = $this->getActualUsername($otherUser); + $this->response = UserHelper::removeUserFromGroup( + $this->getBaseUrl(), + $actualOtherUser, + $group, + $actualUser, + $actualPassword, + $this->getStepLineRef(), + $this->ocsApiVersion + ); + } + + /** + * @When /^the administrator makes user "([^"]*)" a subadmin of group "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $group + * + * @return void + */ + public function adminMakesUserSubadminOfGroupUsingTheProvisioningApi( + string $user, + string $group + ):void { + $user = $this->getActualUsername($user); + $this->userMakesUserASubadminOfGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $user, + $group + ); + } + + /** + * @When user :user makes user :otherUser a subadmin of group :group using the provisioning API + * + * @param string $user + * @param string $otherUser + * @param string $group + * + * @return void + * @throws Exception + */ + public function userMakesUserASubadminOfGroupUsingTheProvisioningApi( + string $user, + string $otherUser, + string $group + ):void { + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $actualSubadminUsername = $this->getActualUsername($otherUser); + + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$actualSubadminUsername/subadmins"; + $body = ['groupid' => $group]; + $this->response = HttpRequestHelper::post( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword, + null, + $body + ); + } + + /** + * @When the administrator gets all the groups where user :user is subadmin using the provisioning API + * + * @param string $user + * + * @return void + */ + public function theAdministratorGetsAllTheGroupsWhereUserIsSubadminUsingTheProvisioningApi(string $user):void { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$user/subadmins"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + } + + /** + * @When /^user "([^"]*)" gets all the groups where user "([^"]*)" is subadmin using the provisioning API$/ + * @When /^user "([^"]*)" tries to get all the groups where user "([^"]*)" is subadmin using the provisioning API$/ + * + * @param string $user + * @param string $otherUser + * + * @return void + * @throws Exception + */ + public function userTriesToGetAllTheGroupsWhereUserIsSubadminUsingTheProvisioningApi(string $user, string $otherUser):void { + $actualOtherUser = $this->getActualUsername($otherUser); + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$actualOtherUser/subadmins"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + } + + /** + * @Given /^user "([^"]*)" has been made a subadmin of group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + */ + public function userHasBeenMadeSubadminOfGroup( + string $user, + string $group + ):void { + $this->adminMakesUserSubadminOfGroupUsingTheProvisioningApi( + $user, + $group + ); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + } + + /** + * @When the administrator gets all the subadmins of group :group using the provisioning API + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function theAdministratorGetsAllTheSubadminsOfGroupUsingTheProvisioningApi(string $group):void { + $this->userGetsAllTheSubadminsOfGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $group + ); + } + + /** + * @When user :user gets all the subadmins of group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userGetsAllTheSubadminsOfGroupUsingTheProvisioningApi(string $user, string $group):void { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/groups/$group/subadmins"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + } + + /** + * @When the administrator removes user :user from being a subadmin of group :group using the provisioning API + * + * @param string $user + * @param string $group + * + * @return void + */ + public function theAdministratorRemovesUserFromBeingASubadminOfGroupUsingTheProvisioningApi( + string $user, + string $group + ):void { + $this->userRemovesUserFromBeingASubadminOfGroupUsingTheProvisioningApi( + $this->getAdminUsername(), + $user, + $group + ); + } + + /** + * @When user :user removes user :otherUser from being a subadmin of group :group using the provisioning API + * + * @param string $user + * @param string $otherUser + * @param string $group + * + * @return void + * @throws Exception + */ + public function userRemovesUserFromBeingASubadminOfGroupUsingTheProvisioningApi( + string $user, + string $otherUser, + string $group + ):void { + $actualOtherUser = $this->getActualUsername($otherUser); + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$actualOtherUser/subadmins"; + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getUserPassword($actualUser); + $this->response = HttpRequestHelper::delete( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword, + null, + ['groupid' => $group] + ); + } + + /** + * @Then /^the users returned by the API should be$/ + * + * @param TableNode $usersList + * + * @return void + * @throws Exception + */ + public function theUsersShouldBe(TableNode $usersList):void { + $this->verifyTableNodeColumnsCount($usersList, 1); + $users = $usersList->getRows(); + $usersSimplified = \array_map( + function ($user) { + return $this->getActualUsername($user); + }, + $this->simplifyArray($users) + ); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->theseUsersShouldBeInTheResponse($usersSimplified); + } else { + $respondedArray = $this->getArrayOfUsersResponded($this->response); + Assert::assertEqualsCanonicalizing( + $usersSimplified, + $respondedArray, + __METHOD__ + . " Provided users do not match the users returned in the response." + ); + } + } + + /** + * @Then /^the users returned by the API should include$/ + * + * @param TableNode $usersList + * + * @return void + * @throws Exception + */ + public function theUsersShouldInclude(TableNode $usersList):void { + $this->verifyTableNodeColumnsCount($usersList, 1); + $users = $usersList->getRows(); + $usersSimplified = \array_map( + function ($user) { + return $this->getActualUsername($user); + }, + $this->simplifyArray($users) + ); + $respondedArray = $this->getArrayOfUsersResponded($this->response); + foreach ($usersSimplified as $userElement) { + Assert::assertContains( + $userElement, + $respondedArray, + __METHOD__ + . " user $userElement is not present in the users list: \n" + . \join("\n", $respondedArray) + ); + } + } + + /** + * @Then /^the groups returned by the API should be$/ + * + * @param TableNode $groupsList + * + * @return void + * @throws Exception + */ + public function theGroupsShouldBe(TableNode $groupsList):void { + $this->verifyTableNodeColumnsCount($groupsList, 1); + $groups = $groupsList->getRows(); + $groupsSimplified = $this->simplifyArray($groups); + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->theseGroupsShouldBeInTheResponse($groupsSimplified); + } else { + Assert::assertEqualsCanonicalizing( + $groupsSimplified, + $respondedArray, + __METHOD__ + . " Provided groups do not match the groups returned in the response." + ); + } + } + + /** + * @Then /^the extra groups returned by the API should be$/ + * + * @param TableNode $groupsList + * + * @return void + * @throws Exception + */ + public function theExtraGroupsShouldBe(TableNode $groupsList):void { + $this->verifyTableNodeColumnsCount($groupsList, 1); + $groups = $groupsList->getRows(); + $groupsSimplified = $this->simplifyArray($groups); + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->theseGroupsShouldBeInTheResponse($groupsSimplified); + } else { + $expectedGroups = \array_merge($this->startingGroups, $groupsSimplified); + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + Assert::assertEqualsCanonicalizing( + $expectedGroups, + $respondedArray, + __METHOD__ + . " Provided groups do not match the groups returned in the response." + ); + } + } + + /** + * @Then /^the groups returned by the API should include "([^"]*)"$/ + * + * @param string $group + * + * @return void + */ + public function theGroupsReturnedByTheApiShouldInclude(string $group):void { + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + Assert::assertContains($group, $respondedArray); + } + + /** + * @Then /^the groups returned by the API should not include "([^"]*)"$/ + * + * @param string $group + * + * @return void + */ + public function theGroupsReturnedByTheApiShouldNotInclude(string $group):void { + $respondedArray = $this->getArrayOfGroupsResponded($this->response); + Assert::assertNotContains($group, $respondedArray); + } + + /** + * @Then /^the users returned by the API should not include "([^"]*)"$/ + * + * @param string $user + * + * @return void + */ + public function theUsersReturnedByTheApiShouldNotInclude(string $user):void { + $respondedArray = $this->getArrayOfUsersResponded($this->response); + Assert::assertNotContains($user, $respondedArray); + } + + /** + * @param TableNode|null $groupsOrUsersList + * + * @return void + * @throws Exception + */ + public function checkSubadminGroupsOrUsersTable(?TableNode $groupsOrUsersList):void { + $this->verifyTableNodeColumnsCount($groupsOrUsersList, 1); + $tableRows = $groupsOrUsersList->getRows(); + $simplifiedTableRows = $this->simplifyArray($tableRows); + $respondedArray = $this->getArrayOfSubadminsResponded($this->response); + Assert::assertEqualsCanonicalizing( + $simplifiedTableRows, + $respondedArray + ); + } + + /** + * @Then /^the subadmin groups returned by the API should be$/ + * + * @param TableNode|null $groupsList + * + * @return void + * @throws Exception + */ + public function theSubadminGroupsShouldBe(?TableNode $groupsList):void { + $this->checkSubadminGroupsOrUsersTable($groupsList); + } + + /** + * @Then /^the subadmin users returned by the API should be$/ + * + * @param TableNode|null $usersList + * + * @return void + * @throws Exception + */ + public function theSubadminUsersShouldBe(?TableNode $usersList):void { + $this->checkSubadminGroupsOrUsersTable($usersList); + } + + /** + * @Then /^the apps returned by the API should include$/ + * + * @param TableNode|null $appList + * + * @return void + * @throws Exception + */ + public function theAppsShouldInclude(?TableNode $appList):void { + $this->verifyTableNodeColumnsCount($appList, 1); + $apps = $appList->getRows(); + $appsSimplified = $this->simplifyArray($apps); + $respondedArray = $this->getArrayOfAppsResponded($this->response); + foreach ($appsSimplified as $app) { + Assert::assertContains( + $app, + $respondedArray, + "The apps returned by the API should include $app but $app was not included" + ); + } + } + + /** + * @Then /^the apps returned by the API should not include$/ + * + * @param TableNode|null $appList + * + * @return void + * @throws Exception + */ + public function theAppsShouldNotInclude(?TableNode $appList):void { + $this->verifyTableNodeColumnsCount($appList, 1); + $apps = $appList->getRows(); + $appsSimplified = $this->simplifyArray($apps); + $respondedArray = $this->getArrayOfAppsResponded($this->response); + foreach ($appsSimplified as $app) { + Assert::assertNotContains( + $app, + $respondedArray, + "The apps returned by the API should not include $app but $app was included" + ); + } + } + + /** + * @Then /^app "([^"]*)" should not be in the apps list$/ + * + * @param string $appName + * + * @return void + */ + public function appShouldNotBeInTheAppsList(string $appName):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/apps"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $respondedArray = $this->getArrayOfAppsResponded($this->response); + Assert::assertNotContains($appName, $respondedArray); + } + + /** + * @Then /^user "([^"]*)" should be a subadmin of group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userShouldBeASubadminOfGroup(string $user, string $group):void { + $this->theAdministratorGetsAllTheSubadminsOfGroupUsingTheProvisioningApi($group); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + $listOfSubadmins = $this->getArrayOfSubadminsResponded($this->response); + Assert::assertContains( + $user, + $listOfSubadmins + ); + } + + /** + * @Then /^user "([^"]*)" should not be a subadmin of group "([^"]*)"$/ + * + * @param string $user + * @param string $group + * + * @return void + * @throws Exception + */ + public function userShouldNotBeASubadminOfGroup(string $user, string $group):void { + $this->theAdministratorGetsAllTheSubadminsOfGroupUsingTheProvisioningApi($group); + $listOfSubadmins = $this->getArrayOfSubadminsResponded($this->response); + Assert::assertNotContains( + $user, + $listOfSubadmins + ); + } + + /** + * @Then /^the display name returned by the API should be "([^"]*)"$/ + * + * @param string $expectedDisplayName + * + * @return void + * @throws Exception + */ + public function theDisplayNameReturnedByTheApiShouldBe(string $expectedDisplayName):void { + $responseDisplayName = (string) $this->getResponseXml(null, __METHOD__)->data[0]->displayname; + Assert::assertEquals( + $expectedDisplayName, + $responseDisplayName + ); + } + + /** + * @Then /^the display name of user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $displayname + * + * @return void + * @throws Exception + */ + public function theDisplayNameOfUserShouldBe(string $user, string $displayname):void { + $actualUser = $this->getActualUsername($user); + $this->retrieveUserInformationAsAdminUsingProvisioningApi($actualUser); + $this->theDisplayNameReturnedByTheApiShouldBe($displayname); + } + + /** + * @Then the display name of the following users should be + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theDisplayNameOfTheFollowingUsersShouldBe(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["name", "display-name"]); + $users = $table->getHash(); + foreach ($users as $user) { + $this->theDisplayNameOfUserShouldBe($user["name"], $user["display-name"]); + } + } + + /** + * @Then /^the display name of user "([^"]*)" should not have changed$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theDisplayNameOfUserShouldNotHaveChanged(string $user):void { + $actualUser = $this->getActualUsername($user); + $expectedDisplayName = $this->getDisplayNameForUser($user); + $this->retrieveUserInformationAsAdminUsingProvisioningApi($actualUser); + $this->theDisplayNameReturnedByTheApiShouldBe($expectedDisplayName); + } + + /** + * @Then /^the email address returned by the API should be "([^"]*)"$/ + * + * @param string $expectedEmailAddress + * + * @return void + * @throws Exception + */ + public function theEmailAddressReturnedByTheApiShouldBe(string $expectedEmailAddress):void { + $responseEmailAddress = (string) $this->getResponseXml(null, __METHOD__)->data[0]->email; + Assert::assertEquals( + $expectedEmailAddress, + $responseEmailAddress + ); + } + + /** + * @Then /^the email address of user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $expectedEmailAddress + * + * @return void + * @throws Exception + */ + public function theEmailAddressOfUserShouldBe(string $user, string $expectedEmailAddress):void { + $user = $this->getActualUsername($user); + $this->retrieveUserInformationAsAdminUsingProvisioningApi($user); + $this->theEmailAddressReturnedByTheApiShouldBe($expectedEmailAddress); + } + + /** + * @Then /^the email address of user "([^"]*)" should not have changed$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function theEmailAddressOfUserShouldNotHaveChanged(string $user):void { + $user = $this->getActualUsername($user); + $expectedEmailAddress = $this->getEmailAddressForUser($user); + $this->retrieveUserInformationAsAdminUsingProvisioningApi($user); + $this->theEmailAddressReturnedByTheApiShouldBe($expectedEmailAddress); + } + + /** + * @Then /^the free, used, total and relative quota returned by the API should exist and be valid numbers$/ + * + * @return void + * @throws Exception + */ + public function theQuotaFieldsReturnedByTheApiShouldBValid():void { + $quotaData = $this->getResponseXml(null, __METHOD__)->data[0]->quota; + $missingQuotaDataString = ""; + if (!isset($quotaData->free)) { + $missingQuotaDataString .= "free "; + } + if (!isset($quotaData->used)) { + $missingQuotaDataString .= "used "; + } + if (!isset($quotaData->total)) { + $missingQuotaDataString .= "total "; + } + if (!isset($quotaData->relative)) { + $missingQuotaDataString .= "relative "; + } + Assert::assertSame( + "", + $missingQuotaDataString, + "These quota data items are missing: $missingQuotaDataString" + ); + $freeQuota = (string) $quotaData->free; + Assert::assertIsNumeric($freeQuota, "free quota '$freeQuota' is not numeric"); + $usedQuota = (string) $quotaData->used; + Assert::assertIsNumeric($usedQuota, "used quota '$usedQuota' is not numeric"); + $totalQuota = (string) $quotaData->total; + Assert::assertIsNumeric($totalQuota, "total quota '$totalQuota' is not numeric"); + $relativeQuota = (string) $quotaData->relative; + Assert::assertIsNumeric($relativeQuota, "free quota '$relativeQuota' is not numeric"); + Assert::assertSame( + (int) $freeQuota + (int) $usedQuota, + (int) $totalQuota, + "free $freeQuota plus used $usedQuota quota is not equal to total quota $totalQuota" + ); + } + + /** + * @Then /^the quota definition returned by the API should be "([^"]*)"$/ + * + * @param string $expectedQuotaDefinition a string that describes the quota + * + * @return void + * @throws Exception + */ + public function theQuotaDefinitionReturnedByTheApiShouldBe(string $expectedQuotaDefinition):void { + $responseQuotaDefinition = (string) $this->getResponseXml(null, __METHOD__)->data[0]->quota->definition; + Assert::assertEquals( + $expectedQuotaDefinition, + $responseQuotaDefinition + ); + } + + /** + * @Then /^the quota definition of user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $expectedQuotaDefinition + * + * @return void + * @throws Exception + */ + public function theQuotaDefinitionOfUserShouldBe(string $user, string $expectedQuotaDefinition):void { + $this->retrieveUserInformationAsAdminUsingProvisioningApi($user); + $this->theQuotaDefinitionReturnedByTheApiShouldBe($expectedQuotaDefinition); + } + + /** + * @Then /^the last login returned by the API should be a current Unix timestamp$/ + * + * @return void + * @throws Exception + */ + public function theLastLoginReturnedByTheApiShouldBe():void { + $responseLastLogin = (string) $this->getResponseXml(null, __METHOD__)->data[0]->last_login; + Assert::assertIsNumeric($responseLastLogin); + Assert::assertGreaterThan($this->scenarioStartTime, (int) $responseLastLogin); + } + + /** + * Parses the xml answer to get the array of users returned. + * + * @param ResponseInterface $resp + * + * @return array + * @throws Exception + */ + public function getArrayOfUsersResponded(ResponseInterface $resp):array { + $listCheckedElements + = $this->getResponseXml($resp, __METHOD__)->data[0]->users[0]->element; + $extractedElementsArray + = \json_decode(\json_encode($listCheckedElements), true); + return $extractedElementsArray; + } + + /** + * Parses the xml answer to get the array of groups returned. + * + * @param ResponseInterface $resp + * + * @return array + * @throws Exception + */ + public function getArrayOfGroupsResponded(ResponseInterface $resp):array { + $listCheckedElements + = $this->getResponseXml($resp, __METHOD__)->data[0]->groups[0]->element; + $extractedElementsArray + = \json_decode(\json_encode($listCheckedElements), true); + return $extractedElementsArray; + } + + /** + * Parses the xml answer to get the array of apps returned. + * + * @param ResponseInterface $resp + * + * @return array + * @throws Exception + */ + public function getArrayOfAppsResponded(ResponseInterface $resp):array { + $listCheckedElements + = $this->getResponseXml($resp, __METHOD__)->data[0]->apps[0]->element; + $extractedElementsArray + = \json_decode(\json_encode($listCheckedElements), true); + return $extractedElementsArray; + } + + /** + * Parses the xml answer to get the array of subadmins returned. + * + * @param ResponseInterface $resp + * + * @return array + * @throws Exception + */ + public function getArrayOfSubadminsResponded(ResponseInterface $resp):array { + $listCheckedElements + = $this->getResponseXml($resp, __METHOD__)->data[0]->element; + $extractedElementsArray + = \json_decode(\json_encode($listCheckedElements), true); + return $extractedElementsArray; + } + + /** + * Parses the xml answer to get the array of app info returned for an app. + * + * @param ResponseInterface $resp + * + * @return array + * @throws Exception + */ + public function getArrayOfAppInfoResponded(ResponseInterface $resp):array { + $listCheckedElements + = $this->getResponseXml($resp, __METHOD__)->data[0]; + $extractedElementsArray + = \json_decode(\json_encode($listCheckedElements), true); + return $extractedElementsArray; + } + + /** + * @Then /^app "([^"]*)" should be disabled$/ + * + * @param string $app + * + * @return void + * @throws Exception + */ + public function appShouldBeDisabled(string $app):void { + $fullUrl = $this->getBaseUrl() + . "/ocs/v2.php/cloud/apps?filter=disabled"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $respondedArray = $this->getArrayOfAppsResponded($this->response); + Assert::assertContains($app, $respondedArray); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + } + + /** + * @Then /^app "([^"]*)" should be enabled$/ + * + * @param string $app + * + * @return void + * @throws Exception + */ + public function appShouldBeEnabled(string $app):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/apps?filter=enabled"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + $respondedArray = $this->getArrayOfAppsResponded($this->response); + Assert::assertContains($app, $respondedArray); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + } + + /** + * @Then /^the information for app "([^"]*)" should have a valid version$/ + * + * @param string $app + * + * @return void + * @throws Exception + */ + public function theInformationForAppShouldHaveAValidVersion(string $app):void { + $fullUrl = $this->getBaseUrl() . "/ocs/v2.php/cloud/apps/$app"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + Assert::assertEquals( + 200, + $this->response->getStatusCode() + ); + $respondedArray = $this->getArrayOfAppInfoResponded($this->response); + Assert::assertArrayHasKey( + 'version', + $respondedArray, + "app info returned for $app app does not have a version" + ); + $appVersion = $respondedArray['version']; + Assert::assertTrue( + \substr_count($appVersion, '.') > 1, + "app version '$appVersion' returned in app info is not a valid version string" + ); + } + + /** + * @Then /^user "([^"]*)" should be disabled$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userShouldBeDisabled(string $user):void { + $user = $this->getActualUsername($user); + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$user"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + Assert::assertEquals( + "false", + $this->getResponseXml(null, __METHOD__)->data[0]->enabled + ); + } + + /** + * @Then the following users should be disabled + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersShouldBeDisabled(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->userShouldBeDisabled($username["username"]); + } + } + + /** + * @Then /^user "([^"]*)" should be enabled$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userShouldBeEnabled(string $user):void { + $user = $this->getActualUsername($user); + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$user"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + Assert::assertEquals( + "true", + $this->getResponseXml(null, __METHOD__)->data[0]->enabled + ); + } + + /** + * @Then the following users should be enabled + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingUsersShouldBeEnabled(TableNode $table):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $this->userShouldBeEnabled($username["username"]); + } + } + + /** + * @When the administrator sets the quota of user :user to :quota using the provisioning API + * + * @param string $user + * @param string $quota + * + * @return void + */ + public function adminSetsUserQuotaToUsingTheProvisioningApi(string $user, string $quota):void { + $user = $this->getActualUsername($user); + $body + = [ + 'key' => 'quota', + 'value' => $quota, + ]; + + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + "PUT", + "/cloud/users/$user", + $this->getStepLineRef(), + $body, + 2 + ); + } + + /** + * @Given the quota of user :user has been set to :quota + * + * @param string $user + * @param string $quota + * + * @return void + */ + public function theQuotaOfUserHasBeenSetTo(string $user, string $quota):void { + $this->adminSetsUserQuotaToUsingTheProvisioningApi($user, $quota); + $this->theHTTPStatusCodeShouldBe(200); + } + + /** + * @When the administrator gives unlimited quota to user :user using the provisioning API + * + * @param string $user + * + * @return void + */ + public function adminGivesUnlimitedQuotaToUserUsingTheProvisioningApi(string $user):void { + $this->adminSetsUserQuotaToUsingTheProvisioningApi($user, 'none'); + } + + /** + * @Given user :user has been given unlimited quota + * + * @param string $user + * + * @return void + */ + public function userHasBeenGivenUnlimitedQuota(string $user):void { + $this->theQuotaOfUserHasBeenSetTo($user, 'none'); + } + + /** + * Returns home path of the given user + * + * @param string $user + * + * @return string + * @throws Exception + */ + public function getUserHome(string $user):string { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$user"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + return $this->getResponseXml(null, __METHOD__)->data[0]->home; + } + + /** + * @Then /^the user attributes returned by the API should include$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function checkUserAttributes(?TableNode $body):void { + $this->verifyTableNodeRows($body, [], $this->userResponseFields); + $bodyRows = $body->getRowsHash(); + foreach ($bodyRows as $field => $value) { + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + $field_array = \explode(' ', $field); + foreach ($field_array as $field_name) { + $data = $data->$field_name; + } + if ($data != $value) { + Assert::fail( + "$field has value $data" + ); + } + } + } + + /** + * @Then the attributes of user :user returned by the API should include + * + * @param string $user + * @param TableNode $body + * + * @return void + * @throws Exception + */ + public function checkAttributesForUser(string $user, TableNode $body):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($body, 2); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $this->getAdminUsername(), + "GET", + "/cloud/users/$user", + null + ); + $this->checkUserAttributes($body); + } + + /** + * @Then /^the API should not return any data$/ + * + * @return void + * @throws Exception + */ + public function theApiShouldNotReturnAnyData():void { + $responseData = $this->getResponseXml(null, __METHOD__)->data[0]; + Assert::assertEmpty( + $responseData, + "Response data is not empty but it should be empty" + ); + } + + /** + * @Then /^the list of users returned by the API should be empty$/ + * + * @return void + * @throws Exception + */ + public function theListOfUsersReturnedByTheApiShouldBeEmpty():void { + $usersList = $this->getResponseXml(null, __METHOD__)->data[0]->users[0]; + Assert::assertEmpty( + $usersList, + "Users list is not empty but it should be empty" + ); + } + + /** + * @Then /^the list of groups returned by the API should be empty$/ + * + * @return void + * @throws Exception + */ + public function theListOfGroupsReturnedByTheApiShouldBeEmpty():void { + $groupsList = $this->getResponseXml(null, __METHOD__)->data[0]->groups[0]; + Assert::assertEmpty( + $groupsList, + "Groups list is not empty but it should be empty" + ); + } + + /** + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function afterScenario():void { + $this->waitForDavRequestsToFinish(); + $this->restoreParametersAfterScenario(); + + if (OcisHelper::isTestingOnOcisOrReva() && $this->someUsersHaveBeenCreated()) { + foreach ($this->getCreatedUsers() as $user) { + OcisHelper::deleteRevaUserData($user["actualUsername"]); + } + } elseif (OcisHelper::isTestingOnOc10()) { + $this->resetAdminUserAttributes(); + } + if ($this->isTestingWithLdap()) { + $this->deleteLdapUsersAndGroups(); + } + $this->cleanupDatabaseUsers(); + $this->cleanupDatabaseGroups(); + } + + /** + * + * @return void + * @throws Exception + */ + public function resetAdminUserAttributes():void { + if ($this->adminDisplayName !== '') { + $this->adminChangesTheDisplayNameOfUserUsingTheProvisioningApi( + $this->getAdminUsername(), + '' + ); + } + if ($this->adminEmailAddress !== '') { + $this->adminChangesTheEmailOfUserToUsingTheProvisioningApi( + $this->getAdminUsername(), + '' + ); + } + } + + /** + * + * @return void + * @throws Exception + */ + public function cleanupDatabaseUsers():void { + $this->authContext->deleteTokenAuthEnforcedAfterScenario(); + $previousServer = $this->currentServer; + $this->usingServer('LOCAL'); + foreach ($this->createdUsers as $user => $userData) { + $this->deleteUser($userData['actualUsername']); + } + $this->usingServer('REMOTE'); + foreach ($this->createdRemoteUsers as $remoteUser => $userData) { + $this->deleteUser($userData['actualUsername']); + } + $this->usingServer($previousServer); + } + + /** + * + * @return void + * @throws Exception + */ + public function cleanupDatabaseGroups():void { + $this->authContext->deleteTokenAuthEnforcedAfterScenario(); + $previousServer = $this->currentServer; + $this->usingServer('LOCAL'); + foreach ($this->createdGroups as $group => $groupData) { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminDeletesGroupWithGroupId( + $groupData['id'] + ); + } else { + $this->cleanupGroup((string)$group); + } + } + $this->usingServer('REMOTE'); + foreach ($this->createdRemoteGroups as $remoteGroup => $groupData) { + if (OcisHelper::isTestingWithGraphApi()) { + $this->graphContext->adminDeletesGroupWithGroupId( + $groupData['id'] + ); + } else { + $this->cleanupGroup((string)$remoteGroup); + } + } + $this->usingServer($previousServer); + } + + /** + * @BeforeScenario + * + * @return void + * @throws Exception + */ + public function rememberAppEnabledDisabledState():void { + if (!OcisHelper::isTestingOnOcisOrReva()) { + SetupHelper::init( + $this->getAdminUsername(), + $this->getAdminPassword(), + $this->getBaseUrl(), + $this->getOcPath() + ); + $this->runOcc(['app:list', '--output json']); + $apps = \json_decode($this->getStdOutOfOccCommand(), true); + $this->enabledApps = \array_keys($apps["enabled"]); + $this->disabledApps = \array_keys($apps["disabled"]); + } + } + + /** + * @BeforeScenario @rememberGroupsThatExist + * + * @return void + * @throws Exception + */ + public function rememberGroupsThatExistAtTheStartOfTheScenario():void { + $this->startingGroups = $this->getArrayOfGroupsResponded($this->getAllGroups()); + } + + /** + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function restoreAppEnabledDisabledState():void { + if (!OcisHelper::isTestingOnOcisOrReva() && !$this->isRunningForDbConversion()) { + $this->runOcc(['app:list', '--output json']); + + $apps = \json_decode($this->getStdOutOfOccCommand(), true); + $currentlyEnabledApps = \array_keys($apps["enabled"]); + $currentlyDisabledApps = \array_keys($apps["disabled"]); + + foreach ($currentlyDisabledApps as $disabledApp) { + if (\in_array($disabledApp, $this->enabledApps)) { + $this->adminEnablesOrDisablesApp('enables', $disabledApp); + } + } + + foreach ($currentlyEnabledApps as $enabledApp) { + if (\in_array($enabledApp, $this->disabledApps)) { + $this->adminEnablesOrDisablesApp('disables', $enabledApp); + } + } + } + } + + /** + * disable or enable user + * + * @param string $user + * @param string $otherUser + * @param string $action + * + * @return void + */ + public function disableOrEnableUser(string $user, string $otherUser, string $action):void { + $actualUser = $this->getActualUsername($user); + $actualPassword = $this->getPasswordForUser($actualUser); + $actualOtherUser = $this->getActualUsername($otherUser); + + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/users/$actualOtherUser/$action"; + $this->response = HttpRequestHelper::put( + $fullUrl, + $this->getStepLineRef(), + $actualUser, + $actualPassword + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * Returns an array of all apps reported by the cloud/apps endpoint + * + * @return array + * @throws Exception + */ + public function getAllApps():array { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/apps"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + return ($this->getArrayOfAppsResponded($this->response)); + } + + /** + * Returns array of enabled apps reported by the cloud/apps endpoint + * + * @return array + * @throws Exception + */ + public function getEnabledApps():array { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/apps?filter=enabled"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + return ($this->getArrayOfAppsResponded($this->response)); + } + + /** + * Returns array of disabled apps reported by the cloud/apps endpoint + * + * @return array + * @throws Exception + */ + public function getDisabledApps():array { + $fullUrl = $this->getBaseUrl() + . "/ocs/v{$this->ocsApiVersion}.php/cloud/apps?filter=disabled"; + $this->response = HttpRequestHelper::get( + $fullUrl, + $this->getStepLineRef(), + $this->getAdminUsername(), + $this->getAdminPassword() + ); + return ($this->getArrayOfAppsResponded($this->response)); + } + + /** + * Removes skeleton directory config from config.php and returns the config value + * + * @param string|null $baseUrl + * + * @return string + * @throws Exception + */ + public function popSkeletonDirectoryConfig(?string $baseUrl = null):string { + $path = $this->getSkeletonDirectory($baseUrl); + $this->runOcc( + ["config:system:delete skeletondirectory"], + null, + null, + $baseUrl + ); + return $path; + } + + /** + * @param string|null $baseUrl + * + * @return string + * @throws Exception + */ + private function getSkeletonDirectory(?string $baseUrl = null):string { + $this->runOcc( + ["config:system:get skeletondirectory"], + null, + null, + $baseUrl + ); + return \trim($this->getStdOutOfOccCommand()); + } + + /** + * Get the name of the smallest available skeleton, to "simulate" without skeleton. + * + * In ownCloud 10 there is always a skeleton directory. If none is specified + * then whatever is in core/skeleton is used. That contains different files + * and folders depending on the build that is being tested. So for testing + * we have "empty" skeleton that is created on-the-fly by the testing app. + * That provides a consistent skeleton for test scenarios that specify + * "without skeleton files" + * + * @return string name of the smallest skeleton folder + */ + private function getSmallestSkeletonDirName(): string { + return "empty"; + } + + /** + * @return bool + */ + private function isEmptySkeleton(): bool { + $skeletonDir = \getenv("SKELETON_DIR"); + if (($skeletonDir !== false) && (\basename($skeletonDir) === $this->getSmallestSkeletonDirName() . "Skeleton")) { + return true; + } + return false; + } + + /** + * sets the skeletondirectory according to the type + * + * @param string $skeletonType can be "tiny", "small", "large" OR empty. + * If an empty string is given, the current + * setting will not be changed + * + * @return string skeleton folder before the change + * @throws Exception + */ + private function setSkeletonDirByType(string $skeletonType): string { + if (OcisHelper::isTestingOnOcisOrReva()) { + $originalSkeletonPath = \getenv("SKELETON_DIR"); + if ($originalSkeletonPath === false) { + $originalSkeletonPath = ''; + } + if ($skeletonType !== '') { + $skeletonDirName = $skeletonType . "Skeleton"; + $newSkeletonPath = \dirname($originalSkeletonPath) . '/' . $skeletonDirName; + \putenv( + "SKELETON_DIR=" . $newSkeletonPath + ); + } + } else { + $baseUrl = $this->getBaseUrl(); + $originalSkeletonPath = $this->getSkeletonDirectory($baseUrl); + + if ($skeletonType !== '') { + OcsApiHelper::sendRequest( + $baseUrl, + $this->getAdminUsername(), + $this->getAdminPassword(), + 'POST', + "/apps/testing/api/v1/testingskeletondirectory", + $this->getStepLineRef(), + [ + 'directory' => $skeletonType . "Skeleton" + ], + $this->getOcsApiVersion() + ); + } + } + return $originalSkeletonPath; + } + + /** + * sets the skeletondirectory + * + * @param string $skeletonDir Full path of the skeleton directory + * If an empty string is given, the current + * setting will not be changed + * + * @return string skeleton folder before the change + * @throws Exception + */ + private function setSkeletonDir(string $skeletonDir): string { + if (OcisHelper::isTestingOnOcisOrReva()) { + $originalSkeletonPath = \getenv("SKELETON_DIR"); + if ($skeletonDir !== '') { + \putenv("SKELETON_DIR=" . $skeletonDir); + } + } else { + $baseUrl = $this->getBaseUrl(); + $originalSkeletonPath = $this->getSkeletonDirectory($baseUrl); + if ($skeletonDir !== '') { + $this->runOcc( + ["config:system:set skeletondirectory --value $skeletonDir"], + null, + null, + $baseUrl + ); + } + } + return $originalSkeletonPath; + } +} diff --git a/tests/acceptance/features/bootstrap/PublicWebDavContext.php b/tests/acceptance/features/bootstrap/PublicWebDavContext.php new file mode 100644 index 00000000000..bdcb48f675a --- /dev/null +++ b/tests/acceptance/features/bootstrap/PublicWebDavContext.php @@ -0,0 +1,1661 @@ + + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use PHPUnit\Framework\Assert; +use TestHelpers\HttpRequestHelper; +use TestHelpers\OcisHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * context file for steps that execute actions as "the public". + */ +class PublicWebDavContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var OccContext + */ + private $occContext; + + /** + * @When /^the public downloads the last public link shared file with range "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $range ignore if empty + * @param string $publicWebDAVAPIVersion + * @param string|null $password + * + * @return void + */ + public function downloadPublicFileWithRange(string $range, string $publicWebDAVAPIVersion, ?string $password = ""):void { + if ($publicWebDAVAPIVersion === "new") { + // In this case a single file has been shared as a public link. + // Even if that file is somewhere down inside a folder(s), when + // accessing it as a public link using the "new" public webDAV API + // the client needs to provide the public link share token followed + // by just the name of the file - not the full path. + $fullPath = $this->featureContext->getLastPublicSharePath(); + $fullPathParts = \explode("/", $fullPath); + $path = \end($fullPathParts); + } else { + $path = ""; + } + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + $password, + $range, + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public downloads the last public link shared file with range "([^"]*)" and password "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $range ignore if empty + * @param string $password + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function downloadPublicFileWithRangeAndPassword(string $range, string $password, string $publicWebDAVAPIVersion):void { + if ($publicWebDAVAPIVersion === "new") { + $path = $this->featureContext->getLastPublicShareData()->data->file_target; + } else { + $path = ""; + } + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + $password, + $range, + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public downloads the last public link shared file using the (old|new) public WebDAV API$/ + * + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function downloadPublicFile(string $publicWebDAVAPIVersion):void { + $this->downloadPublicFileWithRange("", $publicWebDAVAPIVersion); + } + + /** + * @When /^the public downloads the last public link shared file with password "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $password + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function downloadPublicFileWithPassword(string $password, string $publicWebDAVAPIVersion):void { + $this->downloadPublicFileWithRange("", $publicWebDAVAPIVersion, $password); + } + + /** + * @When /^the public deletes (?:file|folder|entry) "([^"]*)" from the last public link share using the (old|new) public WebDAV API$/ + * + * @param string $fileName + * @param string $publicWebDAVAPIVersion + * @param string $password + * + * @return void + */ + public function deleteFileFromPublicShare(string $fileName, string $publicWebDAVAPIVersion, string $password = ""):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-$publicWebDAVAPIVersion" + ); + $fullUrl = $this->featureContext->getBaseUrl() . "/$davPath$fileName"; + $userName = $this->getUsernameForPublicWebdavApi( + $token, + $password, + $publicWebDAVAPIVersion + ); + $headers = [ + 'X-Requested-With' => 'XMLHttpRequest' + ]; + $this->featureContext->setResponse( + HttpRequestHelper::delete( + $fullUrl, + $this->featureContext->getStepLineRef(), + $userName, + $password, + $headers + ) + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @When /^the public deletes file "([^"]*)" from the last public link share using the password "([^"]*)" and (old|new) public WebDAV API$/ + * + * @param string $file + * @param string $password + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicDeletesFileFromTheLastPublicShareUsingThePasswordPasswordAndOldPublicWebdavApi(string $file, string $password, string $publicWebDAVAPIVersion):void { + $this->deleteFileFromPublicShare($file, $publicWebDAVAPIVersion, $password); + } + + /** + * @When /^the public renames (?:file|folder|entry) "([^"]*)" to "([^"]*)" from the last public link share using the (old|new) public WebDAV API$/ + * + * @param string $fileName + * @param string $toFileName + * @param string $publicWebDAVAPIVersion + * @param string|null $password + * + * @return void + */ + public function renameFileFromPublicShare(string $fileName, string $toFileName, string $publicWebDAVAPIVersion, ?string $password = ""):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-$publicWebDAVAPIVersion" + ); + $fullUrl = $this->featureContext->getBaseUrl() . "/$davPath$fileName"; + $destination = $this->featureContext->getBaseUrl() . "/$davPath$toFileName"; + $userName = $this->getUsernameForPublicWebdavApi( + $token, + $password, + $publicWebDAVAPIVersion + ); + $headers = [ + 'X-Requested-With' => 'XMLHttpRequest', + 'Destination' => $destination + ]; + $this->featureContext->setResponse( + HttpRequestHelper::sendRequest( + $fullUrl, + $this->featureContext->getStepLineRef(), + "MOVE", + $userName, + $password, + $headers + ) + ); + } + + /** + * @When /^the public renames file "([^"]*)" to "([^"]*)" from the last public link share using the password "([^"]*)" and (old|new) public WebDAV API$/ + * + * @param string $fileName + * @param string $toName + * @param string $password + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicRenamesFileFromTheLastPublicShareUsingThePasswordPasswordAndOldPublicWebdavApi(string $fileName, string $toName, string $password, string $publicWebDAVAPIVersion):void { + $this->renameFileFromPublicShare($fileName, $toName, $publicWebDAVAPIVersion, $password); + } + + /** + * @When /^the public downloads file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function downloadPublicFileInsideAFolder(string $path, string $publicWebDAVAPIVersion = "old"):void { + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + "", + "", + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public downloads file "([^"]*)" from inside the last public link shared folder with password "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $path + * @param string|null $password + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publicDownloadsTheFileInsideThePublicSharedFolderWithPassword( + string $path, + ?string $password = "", + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + $password, + "", + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public downloads file "([^"]*)" from inside the last public link shared folder with range "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $path + * @param string $range + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function downloadPublicFileInsideAFolderWithRange(string $path, string $range, string $publicWebDAVAPIVersion):void { + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + "", + $range, + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public downloads file "([^"]*)" from inside the last public link shared folder with password "([^"]*)" with range "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $path + * @param string $password + * @param string $range ignored when empty + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + string $path, + string $password, + string $range, + string $publicWebDAVAPIVersion = "old" + ):void { + $path = \ltrim($path, "/"); + $password = $this->featureContext->getActualPassword($password); + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-$publicWebDAVAPIVersion" + ); + $fullUrl = $this->featureContext->getBaseUrl() . "/$davPath$path"; + $userName = $this->getUsernameForPublicWebdavApi( + $token, + $password, + $publicWebDAVAPIVersion + ); + + $headers = [ + 'X-Requested-With' => 'XMLHttpRequest' + ]; + if ($range !== "") { + $headers['Range'] = $range; + } + $response = HttpRequestHelper::get( + $fullUrl, + $this->featureContext->getStepLineRef(), + $userName, + $password, + $headers + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $source target file name + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publiclyUploadingFile(string $source, string $publicWebDAVAPIVersion):void { + $this->publicUploadContent( + \basename($source), + '', + \file_get_contents($source), + false, + [], + $publicWebDAVAPIVersion + ); + } + + /** + * @When the public uploads file :filename using the :publicWebDAVAPIVersion WebDAV API + * + * @param string $source target file name + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicUploadsFileUsingTheWebDAVApi(string $source, string $publicWebDAVAPIVersion):void { + $this->publiclyUploadingFile( + $source, + $publicWebDAVAPIVersion + ); + } + + /** + * + * @param string $baseUrl URL of owncloud + * e.g. http://localhost:8080 + * should include the subfolder + * if owncloud runs in a subfolder + * e.g. http://localhost:8080/owncloud-core + * @param string $source + * @param string $destination + * + * @return void + */ + public function publiclyCopyingFile( + string $baseUrl, + string $source, + string $destination + ):void { + $fullSourceUrl = $baseUrl . $source; + $fullDestUrl = WebDavHelper::sanitizeUrl( + $baseUrl . $destination + ); + + $headers["Destination"] = $fullDestUrl; + $response = HttpRequestHelper::sendRequest( + $fullSourceUrl, + $this->featureContext->getStepLineRef(), + "COPY", + null, + null, + $headers, + null, + null, + null, + false, + 0, + null + ); + $this->featureContext->setResponse($response); + } + + /** + * @When /^the public copies (?:file|folder) "([^"]*)" to "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $source + * @param string $destination + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicCopiesFileUsingTheWebDAVApi(string $source, string $destination, string $publicWebDAVAPIVersion):void { + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-$publicWebDAVAPIVersion" + ); + $baseUrl = $this->featureContext->getLocalBaseUrl() . '/' . $davPath; + + $this->publiclyCopyingFile( + $baseUrl, + $source, + $destination + ); + } + + /** + * @Given the public has uploaded file :filename + * + * @param string $source target file name + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicHasUploadedFileUsingTheWebDAVApi(string $source, string $publicWebDAVAPIVersion):void { + $this->publiclyUploadingFile( + $source, + $publicWebDAVAPIVersion + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * This only works with the old API, auto-rename is not supported in the new API + * auto renaming is handled on files drop folders implicitly + * + * @param string $filename target file name + * @param string $body content to upload + * + * @return void + */ + public function publiclyUploadingContentAutoRename(string $filename, string $body = 'test'):void { + $this->publicUploadContent($filename, '', $body, true); + } + + /** + * @When the public uploads file :filename with content :body with auto-rename mode using the old public WebDAV API + * + * @param string $filename target file name + * @param string $body content to upload + * + * @return void + */ + public function thePublicUploadsFileWithContentWithAutoRenameMode(string $filename, string $body = 'test'):void { + $this->publiclyUploadingContentAutoRename($filename, $body); + } + + /** + * @Given the public has uploaded file :filename with content :body with auto-rename mode + * + * @param string $filename target file name + * @param string $body content to upload + * + * @return void + */ + public function thePublicHasUploadedFileWithContentWithAutoRenameMode(string $filename, string $body = 'test'):void { + $this->publiclyUploadingContentAutoRename($filename, $body); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $filename target file name + * @param string $password + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publiclyUploadingContentWithPassword( + string $filename, + string $password = '', + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publicUploadContent( + $filename, + $password, + $body, + false, + [], + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public uploads file "([^"]*)" with password "([^"]*)" and content "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $filename target file name + * @param string|null $password + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicUploadsFileWithPasswordAndContentUsingPublicWebDAVApi( + string $filename, + ?string $password = '', + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publiclyUploadingContentWithPassword( + $filename, + $password, + $body, + $publicWebDAVAPIVersion + ); + } + + /** + * @Given the public has uploaded file :filename" with password :password and content :body + * + * @param string $filename target file name + * @param string|null $password + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicHasUploadedFileWithPasswordAndContentUsingPublicWebDAVApi( + string $filename, + ?string $password = '', + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publiclyUploadingContentWithPassword( + $filename, + $password, + $body, + $publicWebDAVAPIVersion + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $filename target file name + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publiclyOverwritingContent(string $filename, string $body = 'test', string $publicWebDAVAPIVersion = 'old'):void { + $this->publicUploadContent($filename, '', $body, false, [], $publicWebDAVAPIVersion); + } + + /** + * @When the public overwrites file :filename with content :body using the :type WebDAV API + * + * @param string $filename target file name + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicOverwritesFileWithContentUsingWebDavApi(string $filename, string $body = 'test', string $publicWebDAVAPIVersion = 'old'):void { + $this->publiclyOverwritingContent( + $filename, + $body, + $publicWebDAVAPIVersion + ); + } + + /** + * @Given the public has overwritten file :filename with content :body + * + * @param string $filename target file name + * @param string $body content to upload + * + * @return void + */ + public function thePublicHasOverwrittenFileWithContentUsingOldWebDavApi(string $filename, string $body = 'test'):void { + $this->publiclyOverwritingContent( + $filename, + $body + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $filename target file name + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publiclyUploadingContent( + string $filename, + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publicUploadContent( + $filename, + '', + $body, + false, + [], + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public uploads file "([^"]*)" with content "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $filename target file name + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicUploadsFileWithContentUsingThePublicWebDavApi( + string $filename, + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publiclyUploadingContent( + $filename, + $body, + $publicWebDAVAPIVersion + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @Given the public has uploaded file :filename with content :body + * + * @param string $filename target file name + * @param string $body content to upload + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function thePublicHasUploadedFileWithContentUsingThePublicWebDavApi( + string $filename, + string $body = 'test', + string $publicWebDAVAPIVersion = "old" + ):void { + $this->publiclyUploadingContent( + $filename, + $body, + $publicWebDAVAPIVersion + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should be able to download the last publicly shared file using the (old|new) public WebDAV API without a password and the content should be "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $expectedContent + * + * @return void + * @throws Exception + */ + public function checkLastPublicSharedFileDownload( + string $publicWebDAVAPIVersion, + string $expectedContent + ):void { + $this->checkLastPublicSharedFileWithPasswordDownload( + $publicWebDAVAPIVersion, + "", + $expectedContent + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should be able to download the last publicly shared file using the (old|new) public WebDAV API without a password and the content should be "([^"]*)" plus end-of-line$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $expectedContent + * + * @return void + * @throws Exception + */ + public function checkLastPublicSharedFileDownloadPlusEndOfLine( + string $publicWebDAVAPIVersion, + string $expectedContent + ):void { + $this->checkLastPublicSharedFileWithPasswordDownload( + $publicWebDAVAPIVersion, + "", + "$expectedContent\n" + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should be able to download the last publicly shared file using the (old|new) public WebDAV API with password "([^"]*)" and the content should be "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $expectedContent + * + * @return void + * @throws Exception + */ + public function checkLastPublicSharedFileWithPasswordDownload( + string $publicWebDAVAPIVersion, + string $password, + string $expectedContent + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->downloadPublicFileWithRange( + "", + $publicWebDAVAPIVersion, + $password + ); + + $this->featureContext->checkDownloadedContentMatches( + $expectedContent, + "Checking the content of the last public shared file after downloading with the $publicWebDAVAPIVersion public WebDAV API" + ); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public download of the last publicly shared file using the (old|new) public WebDAV API with password "([^"]*)" should fail with HTTP status code "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $expectedHttpCode + * + * @return void + */ + public function theLastPublicSharedFileShouldNotBeAbleToBeDownloadedWithPassword( + string $publicWebDAVAPIVersion, + string $password, + string $expectedHttpCode + ):void { + $this->downloadPublicFileWithRange( + "", + $publicWebDAVAPIVersion, + $password + ); + $responseContent = $this->featureContext->getResponse()->getBody()->getContents(); + \libxml_use_internal_errors(true); + Assert::assertNotFalse( + \simplexml_load_string($responseContent), + "response body is not valid XML, maybe download did work\n" . + "response body: \n$responseContent\n" + ); + $this->featureContext->theHTTPStatusCodeShouldBe($expectedHttpCode); + } + + /** + * @Then /^the public download of the last publicly shared file using the (old|new) public WebDAV API without a password should fail with HTTP status code "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $expectedHttpCode + * + * @return void + */ + public function theLastPublicSharedFileShouldNotBeAbleToBeDownloadedWithoutAPassword( + string $publicWebDAVAPIVersion, + string $expectedHttpCode + ):void { + $this->theLastPublicSharedFileShouldNotBeAbleToBeDownloadedWithPassword( + $publicWebDAVAPIVersion, + "", + $expectedHttpCode + ); + } + + /** + * @Then /^the public should be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function shouldBeAbleToDownloadFileInsidePublicSharedFolder( + string $path, + string $publicWebDAVAPIVersion + ):void { + $this->shouldBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + "", + $path, + $publicWebDAVAPIVersion, + "" + ); + } + + /** + * @Then /^the public should not be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API without a password$/ + * @Then /^the public download of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API should fail with HTTP status code "([^"]*)"$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $expectedHttpCode + * + * @return void + */ + public function shouldNotBeAbleToDownloadFileInsidePublicSharedFolder( + string $path, + string $publicWebDAVAPIVersion, + string $expectedHttpCode = "401" + ):void { + $this->shouldNotBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + "", + $path, + $publicWebDAVAPIVersion, + "", + $expectedHttpCode + ); + } + + /** + * @Then /^the public should be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)"$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * + * @return void + */ + public function shouldBeAbleToDownloadFileInsidePublicSharedFolderWithPassword( + string $path, + string $publicWebDAVAPIVersion, + string $password + ):void { + $this->shouldBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + "", + $path, + $publicWebDAVAPIVersion, + $password + ); + } + + /** + * @Then /^the public should be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)" and the content should be "([^"]*)" plus end-of-line$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $content + * + * @return void + * @throws Exception + */ + public function shouldBeAbleToDownloadFileInsidePublicSharedFolderWithPasswordAndEOL( + string $path, + string $publicWebDAVAPIVersion, + string $password, + string $content + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPassword( + $path, + $password, + $publicWebDAVAPIVersion + ); + + $this->featureContext->downloadedContentShouldBePlusEndOfLine($content); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)" and the content should be "([^"]*)"$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $content + * + * @return void + * @throws Exception + */ + public function shouldBeAbleToDownloadFileInsidePublicSharedFolderWithPasswordAndContentShouldBe( + string $path, + string $publicWebDAVAPIVersion, + string $password, + string $content + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPassword( + $path, + $password, + $publicWebDAVAPIVersion + ); + + $this->featureContext->downloadedContentShouldBe($content); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API without password and the content should be "([^"]*)"$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $content + * + * @return void + * @throws Exception + */ + public function shouldBeAbleToDownloadFileInsidePublicSharedFolderWithoutPassword( + string $path, + string $publicWebDAVAPIVersion, + string $content + ):void { + $this->shouldBeAbleToDownloadFileInsidePublicSharedFolderWithPasswordAndContentShouldBe($path, $publicWebDAVAPIVersion, "", $content); + } + + /** + * @Then /^the public should not be able to download file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)"$/ + * @Then /^the public download of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)" should fail with HTTP status code "([^"]*)"$/ + * + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $expectedHttpCode + * + * @return void + */ + public function shouldNotBeAbleToDownloadFileInsidePublicSharedFolderWithPassword( + string $path, + string $publicWebDAVAPIVersion, + string $password, + string $expectedHttpCode = "401" + ):void { + $this->shouldNotBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + "", + $path, + $publicWebDAVAPIVersion, + $password, + $expectedHttpCode + ); + } + + /** + * @Then /^the public should be able to download the range "([^"]*)" of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)""$/ + * + * @param string $range + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * + * @return void + * @throws Exception + */ + public function shouldBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + string $range, + string $path, + string $publicWebDAVAPIVersion, + string $password + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + $password, + $range, + $publicWebDAVAPIVersion + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public should not be able to download the range "([^"]*)" of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)"$/ + * + * @param string $range + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $expectedHttpCode + * + * @return void + * @throws Exception + */ + public function shouldNotBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + string $range, + string $path, + string $publicWebDAVAPIVersion, + string $password, + string $expectedHttpCode = "401" + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicDownloadsTheFileInsideThePublicSharedFolderWithPasswordAndRange( + $path, + $password, + $range, + $publicWebDAVAPIVersion + ); + + $responseContent = $this->featureContext->getResponse()->getBody()->getContents(); + \libxml_use_internal_errors(true); + Assert::assertNotFalse( + \simplexml_load_string($responseContent), + "response body is not valid XML, maybe download did work\n" . + "response body: \n$responseContent\n" + ); + $this->featureContext->theHTTPStatusCodeShouldBe($expectedHttpCode); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + } + + /** + * @Then /^the public should be able to download the range "([^"]*)" of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API and the content should be "([^"]*)"$/ + * + * @param string $range + * @param string $path + * @param string $publicWebDAVAPIVersion + * @param string $content + * + * @return void + * @throws Exception + */ + public function shouldBeAbleToDownloadRangeOfFileInsidePublicSharedFolder( + string $range, + string $path, + string $publicWebDAVAPIVersion, + string $content + ):void { + $this->shouldBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + $range, + $path, + $publicWebDAVAPIVersion, + "", + $content + ); + } + + /** + * @Then /^the public should not be able to download the range "([^"]*)" of file "([^"]*)" from inside the last public link shared folder using the (old|new) public WebDAV API without a password$/ + * + * @param string $range + * @param string $path + * @param string $publicWebDAVAPIVersion + * + * @return void + * @throws Exception + */ + public function shouldNotBeAbleToDownloadRangeOfFileInsidePublicSharedFolder( + string $range, + string $path, + string $publicWebDAVAPIVersion + ):void { + $this->shouldNotBeAbleToDownloadRangeOfFileInsidePublicSharedFolderWithPassword( + $range, + $path, + $publicWebDAVAPIVersion, + "" + ); + } + + /** + * @Then /^the public upload to the last publicly shared file using the (old|new) public WebDAV API should (?:fail|pass) with HTTP status code "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $expectedHttpCode + * + * @return void + * @throws Exception + */ + public function publiclyUploadingShouldToSharedFileShouldFail( + string $publicWebDAVAPIVersion, + string $expectedHttpCode + ):void { + $filename = ""; + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $filename = (string)$this->featureContext->getLastPublicShareData()->data[0]->file_target; + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicUploadContent( + $filename, + '', + 'test', + false, + [], + $publicWebDAVAPIVersion + ); + + $this->featureContext->theHTTPStatusCodeShouldBe($expectedHttpCode); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + } + + /** + * @Then /^uploading a file should not work using the (old|new) public WebDAV API$/ + * @Then /^the public upload to the last publicly shared folder using the (old|new) public WebDAV API should fail with HTTP status code "([^"]*)"$/ + * + * @param string $publicWebDAVAPIVersion + * @param string $expectedHttpCode + * + * @return void + * @throws Exception + */ + public function publiclyUploadingShouldNotWork( + string $publicWebDAVAPIVersion, + string $expectedHttpCode = null + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicUploadContent( + 'whateverfilefortesting.txt', + '', + 'test', + false, + [], + $publicWebDAVAPIVersion + ); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + + $response = $this->featureContext->getResponse(); + if ($expectedHttpCode === null) { + $expectedHttpCode = [507, 400, 401, 403, 404, 423]; + } + $this->featureContext->theHTTPStatusCodeShouldBe( + $expectedHttpCode, + "upload should have failed but passed with code " . + $response->getStatusCode() + ); + } + + /** + * @Then /^the public should be able to upload file "([^"]*)" into the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)"$/ + * + * @param string $filename + * @param string $publicWebDAVAPIVersion + * @param string $password + * + * @return void + */ + public function publiclyUploadingIntoFolderWithPasswordShouldWork( + string $filename, + string $publicWebDAVAPIVersion, + string $password + ):void { + $this->publicUploadContent( + $filename, + $password, + 'test', + false, + [], + $publicWebDAVAPIVersion + ); + + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public upload of file "([^"]*)" into the last public link shared folder using the (old|new) public WebDAV API with password "([^"]*)" should fail with HTTP status code "([^"]*)"$/ + * + * @param string $filename + * @param string $publicWebDAVAPIVersion + * @param string $password + * @param string $expectedHttpCode + * + * @return void + */ + public function publiclyUploadingIntoFolderWithPasswordShouldFail( + string $filename, + string $publicWebDAVAPIVersion, + string $password, + string $expectedHttpCode + ):void { + $this->publicUploadContent( + $filename, + $password, + 'test', + false, + [], + $publicWebDAVAPIVersion + ); + + $response = $this->featureContext->getResponse(); + $this->featureContext->theHTTPStatusCodeShouldBe( + $expectedHttpCode, + "upload of $filename into the last publicly shared folder should have failed with code " . + $expectedHttpCode . " but the code was " . $response->getStatusCode() + ); + } + + /** + * @Then /^uploading a file should work using the (old|new) public WebDAV API$/ + * + * @param string $publicWebDAVAPIVersion + * + * @return void + * @throws Exception + */ + public function publiclyUploadingShouldWork(string $publicWebDAVAPIVersion):void { + $path = "whateverfilefortesting-$publicWebDAVAPIVersion-publicWebDAVAPI.txt"; + $content = "test $publicWebDAVAPIVersion"; + + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + } else { + $techPreviewHadToBeEnabled = false; + } + + $this->publicUploadContent( + $path, + '', + $content, + false, + [], + $publicWebDAVAPIVersion + ); + $response = $this->featureContext->getResponse(); + Assert::assertTrue( + ($response->getStatusCode() == 201), + "upload should have passed but failed with code " . + $response->getStatusCode() + ); + $this->shouldBeAbleToDownloadFileInsidePublicSharedFolder( + $path, + $publicWebDAVAPIVersion + ); + $this->featureContext->checkDownloadedContentMatches($content); + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + } + + /** + * @Then /^uploading content to a public link shared file should (not|)\s?work using the (old|new) public WebDAV API$/ + * + * @param string $shouldOrNot (not|) + * @param string $publicWebDAVAPIVersion + * + * @return void + * @throws Exception + */ + public function publiclyUploadingToPublicLinkSharedFileShouldWork( + string $shouldOrNot, + string $publicWebDAVAPIVersion + ):void { + $content = "test $publicWebDAVAPIVersion"; + $should = ($shouldOrNot !== "not"); + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } elseif ($publicWebDAVAPIVersion === "new") { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + $path = $this->featureContext->getLastPublicSharePath(); + } else { + $techPreviewHadToBeEnabled = false; + $path = ""; + } + + $this->publicUploadContent( + $path, + '', + $content, + false, + [], + $publicWebDAVAPIVersion + ); + $response = $this->featureContext->getResponse(); + if ($should) { + Assert::assertTrue( + ($response->getStatusCode() == 204), + "upload should have passed but failed with code " . + $response->getStatusCode() + ); + + $this->downloadPublicFileWithRange( + "", + $publicWebDAVAPIVersion, + "" + ); + + $this->featureContext->checkDownloadedContentMatches( + $content, + "Checking the content of the last public shared file after downloading with the $publicWebDAVAPIVersion public WebDAV API" + ); + } else { + $expectedCode = 403; + Assert::assertTrue( + ($response->getStatusCode() == $expectedCode), + "upload should have failed with HTTP status $expectedCode but passed with code " . + $response->getStatusCode() + ); + } + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + } + + /** + * @When the public uploads file :fileName to the last public link shared folder with mtime :mtime using the :davVersion public WebDAV API + * + * @param String $fileName + * @param String $mtime + * @param String $davVersion + * + * @return void + * @throws Exception + */ + public function thePublicUploadsFileToLastSharedFolderWithMtimeUsingTheWebdavApi( + string $fileName, + string $mtime, + string $davVersion = "old" + ):void { + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + + $this->publicUploadContent( + $fileName, + '', + 'test', + false, + ["X-OC-Mtime" => $mtime], + $davVersion + ); + } + + /** + * @param string $destination + * @param string $password + * + * @return void + */ + public function publicCreatesFolderUsingPassword( + string $destination, + string $password + ):void { + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-new" + ); + $url = $this->featureContext->getBaseUrl() . "/$davPath"; + $password = $this->featureContext->getActualPassword($password); + $userName = $this->getUsernameForPublicWebdavApi( + $token, + $password, + "new" + ); + $foldername = \implode( + '/', + \array_map('rawurlencode', \explode('/', $destination)) + ); + $url .= \ltrim($foldername, '/'); + + $this->featureContext->setResponse( + HttpRequestHelper::sendRequest( + $url, + $this->featureContext->getStepLineRef(), + 'MKCOL', + $userName, + $password + ) + ); + } + + /** + * @When the public creates folder :destination using the new public WebDAV API + * + * @param String $destination + * + * @return void + */ + public function publicCreatesFolder(string $destination):void { + $this->publicCreatesFolderUsingPassword($destination, ''); + } + + /** + * @Then /^the public should be able to create folder "([^"]*)" in the last public link shared folder using the new public WebDAV API with password "([^"]*)"$/ + * + * @param string $foldername + * @param string $password + * + * @return void + */ + public function publicShouldBeAbleToCreateFolderWithPassword( + string $foldername, + string $password + ):void { + $this->publicCreatesFolderUsingPassword($foldername, $password); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the public creation of folder "([^"]*)" in the last public link shared folder using the new public WebDAV API with password "([^"]*)" should fail with HTTP status code "([^"]*)"$/ + * + * @param string $foldername + * @param string $password + * @param string $expectedHttpCode + * + * @return void + */ + public function publicCreationOfFolderWithPasswordShouldFail( + string $foldername, + string $password, + string $expectedHttpCode + ):void { + $this->publicCreatesFolderUsingPassword($foldername, $password); + $this->featureContext->theHTTPStatusCodeShouldBe( + $expectedHttpCode, + "creation of $foldername in the last publicly shared folder should have failed with code " . + $expectedHttpCode + ); + } + + /** + * @Then the mtime of file :fileName in the last shared public link using the WebDAV API should be :mtime + * + * @param string $fileName + * @param string $mtime + * + * @return void + * @throws Exception + */ + public function theMtimeOfFileInTheLastSharedPublicLinkUsingTheWebdavApiShouldBe( + string $fileName, + string $mtime + ):void { + $token = $this->featureContext->getLastPublicShareToken(); + $baseUrl = $this->featureContext->getBaseUrl(); + if (\TestHelpers\OcisHelper::isTestingOnOcisOrReva()) { + $mtime = \explode(" ", $mtime); + \array_pop($mtime); + $mtime = \implode(" ", $mtime); + Assert::assertStringContainsString( + $mtime, + WebDavHelper::getMtimeOfFileinPublicLinkShare( + $baseUrl, + $fileName, + $token, + $this->featureContext->getStepLineRef() + ) + ); + } else { + Assert::assertEquals( + $mtime, + WebDavHelper::getMtimeOfFileinPublicLinkShare( + $baseUrl, + $fileName, + $token, + $this->featureContext->getStepLineRef() + ) + ); + } + } + + /** + * @Then the mtime of file :fileName in the last shared public link using the WebDAV API should not be :mtime + * + * @param string $fileName + * @param string $mtime + * + * @return void + * @throws Exception + */ + public function theMtimeOfFileInTheLastSharedPublicLinkUsingTheWebdavApiShouldNotBe( + string $fileName, + string $mtime + ):void { + $token = $this->featureContext->getLastPublicShareToken(); + $baseUrl = $this->featureContext->getBaseUrl(); + Assert::assertNotEquals( + $mtime, + WebDavHelper::getMtimeOfFileinPublicLinkShare( + $baseUrl, + $fileName, + $token, + $this->featureContext->getStepLineRef() + ) + ); + } + + /** + * Uploads a file through the public WebDAV API and sets the response in FeatureContext + * + * @param string $filename + * @param string|null $password + * @param string $body + * @param bool $autoRename + * @param array $additionalHeaders + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publicUploadContent( + string $filename, + ?string $password = '', + string $body = 'test', + bool $autoRename = false, + array $additionalHeaders = [], + string $publicWebDAVAPIVersion = "old" + ):void { + if (OcisHelper::isTestingOnOcisOrReva() && $publicWebDAVAPIVersion === "old") { + return; + } + $password = $this->featureContext->getActualPassword($password); + $token = $this->featureContext->getLastPublicShareToken(); + $davPath = WebDavHelper::getDavPath( + $token, + 0, + "public-files-$publicWebDAVAPIVersion" + ); + $url = $this->featureContext->getBaseUrl() . "/$davPath"; + $userName = $this->getUsernameForPublicWebdavApi( + $token, + $password, + $publicWebDAVAPIVersion + ); + + $filename = \implode( + '/', + \array_map('rawurlencode', \explode('/', $filename)) + ); + $url .= \ltrim($filename, '/'); + // Trim any "/" from the end. For example, if we are putting content to a + // single file that has been shared with a link, then the URL should end + // with the link token and no "/" at the end. + $url = \rtrim($url, "/"); + $headers = ['X-Requested-With' => 'XMLHttpRequest']; + + if ($autoRename) { + $headers['OC-Autorename'] = 1; + } + $headers = \array_merge($headers, $additionalHeaders); + $response = HttpRequestHelper::put( + $url, + $this->featureContext->getStepLineRef(), + $userName, + $password, + $headers, + $body + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $token + * @param string $password + * @param string $publicWebDAVAPIVersion + * + * @return string|null + */ + private function getUsernameForPublicWebdavApi( + string $token, + string $password, + string $publicWebDAVAPIVersion + ):?string { + if ($publicWebDAVAPIVersion === "old") { + $userName = $token; + } else { + if ($password !== '') { + $userName = 'public'; + } else { + $userName = null; + } + } + return $userName; + } + + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function setUpScenario(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + $this->occContext = $environment->getContext('OccContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/SearchContext.php b/tests/acceptance/features/bootstrap/SearchContext.php new file mode 100644 index 00000000000..173c917586d --- /dev/null +++ b/tests/acceptance/features/bootstrap/SearchContext.php @@ -0,0 +1,192 @@ + + * @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use TestHelpers\OcisHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * context containing search related API steps + */ +class SearchContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @When user :user searches for :pattern using the WebDAV API + * @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API + * @When user :user searches for :pattern using the WebDAV API requesting these properties: + * @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API requesting these properties: + * + * @param string $user + * @param string $pattern + * @param string|null $limit + * @param TableNode|null $properties + * + * @return void + */ + public function userSearchesUsingWebDavAPI( + string $user, + string $pattern, + ?string $limit = null, + TableNode $properties = null + ):void { + // Because indexing of newly uploaded files or directories with ocis is decoupled and occurs asynchronously, a short wait is necessary before searching files or folders. + if (OcisHelper::isTestingOnOcis()) { + sleep(4); + } + $user = $this->featureContext->getActualUsername($user); + $baseUrl = $this->featureContext->getBaseUrl(); + $password = $this->featureContext->getPasswordForUser($user); + $body + = "\n" . + " \n" . + " \n" . + " $pattern\n"; + if ($limit !== null) { + $body .= " $limit\n"; + } + + $body .= " \n"; + if ($properties !== null) { + $propertiesRows = $properties->getRows(); + $body .= " "; + foreach ($propertiesRows as $property) { + $body .= "<$property[0]/>"; + } + $body .= " "; + } + $body .= " "; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "REPORT", + "/", + null, + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion() + ); + $this->featureContext->setResponse($response); + } + + /** + * @Then file/folder :path in the search result of user :user should contain these properties: + * + * @param string $path + * @param string $user + * @param TableNode $properties + * + * @return void + * @throws Exception + */ + public function fileOrFolderInTheSearchResultShouldContainProperties( + string $path, + string $user, + TableNode $properties + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumns($properties, ['name', 'value']); + $properties = $properties->getHash(); + $fileResult = $this->featureContext->findEntryFromPropfindResponse( + $path, + $user, + "REPORT", + ); + Assert::assertNotFalse( + $fileResult, + "could not find file/folder '$path'" + ); + $fileProperties = $fileResult['value'][1]['value'][0]['value']; + foreach ($properties as $property) { + $foundProperty = false; + $property['value'] = $this->featureContext->substituteInLineCodes( + $property['value'], + $user + ); + foreach ($fileProperties as $fileProperty) { + if ($fileProperty['name'] === $property['name']) { + Assert::assertMatchesRegularExpression( + "/" . $property['value'] . "/", + $fileProperty['value'] + ); + $foundProperty = true; + break; + } + } + Assert::assertTrue( + $foundProperty, + "could not find property '" . $property['name'] . "'" + ); + } + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } + + /** + * @Then the search result by tags for user :user should contain these entries: + * + * @param string|null $user + * @param TableNode $expectedEntries + * + * @return void + * @throws Exception + */ + public function theSearchResultByTagsForUserShouldContainTheseEntries( + ?string $user, + TableNode $expectedEntries + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumnsCount($expectedEntries, 1); + $expectedEntries = $expectedEntries->getRows(); + $expectedEntriesArray = []; + $responseResourcesArray = $this->featureContext->findEntryFromReportResponse($user); + foreach ($expectedEntries as $item) { + \array_push($expectedEntriesArray, $item[0]); + } + Assert::assertEqualsCanonicalizing($expectedEntriesArray, $responseResourcesArray); + } +} diff --git a/tests/acceptance/features/bootstrap/ShareesContext.php b/tests/acceptance/features/bootstrap/ShareesContext.php new file mode 100644 index 00000000000..1bd1f7dfb10 --- /dev/null +++ b/tests/acceptance/features/bootstrap/ShareesContext.php @@ -0,0 +1,231 @@ + + * @author Sergio Bertolin + * @author Phillip Davis + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; + +require_once 'bootstrap.php'; + +/** + * Sharees context. + */ +class ShareesContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var OCSContext + */ + private $ocsContext; + + /** + * @When /^the user gets the sharees using the sharing API with parameters$/ + * + * @param TableNode $body + * + * @return void + */ + public function theUserGetsTheShareesWithParameters(TableNode $body):void { + $this->userGetsTheShareesWithParameters( + $this->featureContext->getCurrentUser(), + $body + ); + } + + /** + * @When /^user "([^"]*)" gets the sharees using the sharing API with parameters$/ + * + * @param string $user + * @param TableNode $body + * + * @return void + * @throws Exception + */ + public function userGetsTheShareesWithParameters(string $user, TableNode $body):void { + $user = $this->featureContext->getActualUsername($user); + $url = '/apps/files_sharing/api/v1/sharees'; + $this->featureContext->verifyTableNodeColumnsCount($body, 2); + if ($body instanceof TableNode) { + $parameters = []; + foreach ($body->getRowsHash() as $key => $value) { + $parameters[] = "$key=$value"; + } + if (!empty($parameters)) { + $url .= '?' . \implode('&', $parameters); + } + } + + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + 'GET', + $url, + null + ); + } + + /** + * @Then /^the "([^"]*)" sharees returned should be$/ + * + * @param string $shareeType + * @param TableNode $shareesList + * + * @return void + * @throws Exception + */ + public function theShareesReturnedShouldBe(string $shareeType, TableNode $shareesList):void { + $this->featureContext->verifyTableNodeColumnsCount($shareesList, 3); + $sharees = $shareesList->getRows(); + $respondedArray = $this->getArrayOfShareesResponded( + $this->featureContext->getResponse(), + $shareeType + ); + Assert::assertEquals( + $sharees, + $respondedArray, + "Returned sharees do not match the expected ones. See the differences below." + ); + } + + /** + * @Then /^the "([^"]*)" sharees returned should include$/ + * + * @param string $shareeType + * @param TableNode $shareesList + * + * @return void + * @throws Exception + */ + public function theShareesReturnedShouldInclude(string $shareeType, TableNode $shareesList):void { + $this->featureContext->verifyTableNodeColumnsCount($shareesList, 3); + $sharees = $shareesList->getRows(); + $respondedArray = $this->getArrayOfShareesResponded( + $this->featureContext->getResponse(), + $shareeType + ); + foreach ($sharees as $sharee) { + Assert::assertContains( + $sharee, + $respondedArray, + "Returned sharees do not match the expected ones. See the differences below." + ); + } + } + + /** + * @Then /^the "([^"]*)" sharees returned should be empty$/ + * + * @param string $shareeType + * + * @return void + */ + public function theShareesReturnedShouldBeEmpty(string $shareeType):void { + $respondedArray = $this->getArrayOfShareesResponded( + $this->featureContext->getResponse(), + $shareeType + ); + if (isset($respondedArray[0])) { + // [0] is display name and [2] is user or group id + $firstEntry = $respondedArray[0][0] . " (" . $respondedArray[0][2] . ")"; + } else { + $firstEntry = ""; + } + + Assert::assertEmpty( + $respondedArray, + "'$shareeType' array should be empty, but it starts with $firstEntry" + ); + } + + /** + * @param ResponseInterface $response + * @param string $shareeType + * + * @return array + * @throws Exception + */ + public function getArrayOfShareesResponded( + ResponseInterface $response, + string $shareeType + ):array { + $elements = $this->featureContext->getResponseXml($response, __METHOD__)->data; + $elements = \json_decode(\json_encode($elements), true); + if (\strpos($shareeType, 'exact ') === 0) { + $elements = $elements['exact']; + $shareeType = \substr($shareeType, 6); + } + + Assert::assertArrayHasKey( + $shareeType, + $elements, + __METHOD__ . " The sharees response does not have key '$shareeType'" + ); + + $sharees = []; + foreach ($elements[$shareeType] as $element) { + if (\is_int(\key($element))) { + // this is a list of elements instead of just one item, + // so return the list + foreach ($element as $innerItem) { + $sharees[] = [ + $innerItem['label'], + $innerItem['value']['shareType'], + $innerItem['value']['shareWith'] + ]; + } + } else { + $sharees[] = [ + $element['label'], + $element['value']['shareType'], + $element['value']['shareWith'] + ]; + } + } + return $sharees; + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + $this->ocsContext = $environment->getContext('OCSContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/Sharing.php b/tests/acceptance/features/bootstrap/Sharing.php new file mode 100644 index 00000000000..918976782d0 --- /dev/null +++ b/tests/acceptance/features/bootstrap/Sharing.php @@ -0,0 +1,4391 @@ + + * @author Sergio Bertolin + * @author Phillip Davis + * @copyright Copyright (c) 2018, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; +use TestHelpers\OcsApiHelper; +use TestHelpers\OcisHelper; +use TestHelpers\SharingHelper; +use TestHelpers\HttpRequestHelper; +use TestHelpers\SetupHelper; +use TestHelpers\TranslationHelper; + +/** + * Sharing trait + */ +trait Sharing { + /** + * @var int + */ + private $sharingApiVersion = 1; + + /** + * Contains the API response to the last share that was created by each user + * using the Sharing API. Shares created on the webUI do not have an entry. + * + * @var SimpleXMLElement[] + */ + private $lastShareDataByUser = []; + + /** + * Contains the share id of the last share that was created by each user, + * either using the Sharing API or on the web UI. + * + * @var string[] + */ + private $lastShareIdByUser = []; + + /** + * @var string + */ + private $userWhoCreatedLastShare = null; + + /** + * @var string + */ + private $userWhoCreatedLastPublicShare = null; + + /** + * Contains the API response to the last public link share that was created + * by the test-runner using the Sharing API. + * Shares created on the webUI do not have an entry. + * + * @var SimpleXMLElement + */ + private $lastPublicShareData = null; + + /** + * Contains the share id of the last public link share that was created by + * the test-runner, either using the Sharing API or on the web UI. + * + * @var string + */ + private $lastPublicShareId = null; + + /** + * @var int + */ + private $savedShareId = null; + + /** + * @var int + */ + private $localLastShareTime = null; + + /** + * Defines the fields that can be provided in a share request. + * + * @var array + */ + private $shareFields = [ + 'path', 'name', 'publicUpload', 'password', 'expireDate', + 'expireDateAsString', 'permissions', 'shareWith', 'shareType' + ]; + + /** + * Defines the fields that are known and can be tested in a share response. + * Note that ownCloud10 also provides file_parent in responses. + * file_parent is not provided by OCIS/reva. + * There are no known clients that use file_parent. + * The acceptance tests do not test for file_parent. + * + * @var array fields that are possible in a share response + */ + private $shareResponseFields = [ + 'id', 'share_type', 'uid_owner', 'displayname_owner', 'stime', 'parent', + 'expiration', 'token', 'uid_file_owner', 'displayname_file_owner', 'path', + 'item_type', 'mimetype', 'storage_id', 'storage', 'item_source', + 'file_source', 'file_target', 'name', 'url', 'mail_send', + 'attributes', 'permissions', 'share_with', 'share_with_displayname', 'share_with_additional_info' + ]; + + /* + * Contains information about the public links that have been created with the webUI. + * Each entry in the array has a "name", "url" and "path". + */ + private $createdPublicLinks = []; + + /** + * @return array + */ + public function getCreatedPublicLinks():array { + return $this->createdPublicLinks; + } + + /** + * The end (last) entry will itself be an array with keys "name", "url" and "path" + * + * @return array + */ + public function getLastCreatedPublicLink():array { + return \end($this->createdPublicLinks); + } + + /** + * @return string + */ + public function getLastCreatedPublicLinkUrl():string { + $lastCreatedLink = $this->getLastCreatedPublicLink(); + return $lastCreatedLink["url"]; + } + + /** + * @return string + */ + public function getLastCreatedPublicLinkPath():string { + $lastCreatedLink = $this->getLastCreatedPublicLink(); + return $lastCreatedLink["path"]; + } + + /** + * @return string + */ + public function getLastCreatedPublicLinkToken():string { + $lastCreatedLinkUrl = $this->getLastCreatedPublicLinkUrl(); + // The token is the last part of the URL, delimited by "/" + $urlParts = \explode("/", $lastCreatedLinkUrl); + return \end($urlParts); + } + + /** + * @return SimpleXMLElement|null + */ + public function getLastPublicShareData():?SimpleXMLElement { + return $this->lastPublicShareData; + } + + /** + * @param SimpleXMLElement $responseXml + * + * @return void + */ + public function setLastPublicShareData(SimpleXMLElement $responseXml): void { + $this->lastPublicShareData = $responseXml; + } + + /** + * @return SimpleXMLElement + * @throws Exception + */ + public function getLastShareData():SimpleXMLElement { + return $this->getLastShareDataForUser($this->userWhoCreatedLastShare); + } + + /** + * @param string|null $user + * + * @return SimpleXMLElement + * @throws Exception + */ + public function getLastShareDataForUser(?string $user):SimpleXMLElement { + if ($user === null) { + throw new Exception( + __METHOD__ . " user not specified. Probably no user or group shares have been created yet in the test scenario." + ); + } + if (isset($this->lastShareDataByUser[$user])) { + return $this->lastShareDataByUser[$user]; + } else { + throw new Exception(__METHOD__ . " last share data for user '$user' was not found"); + } + } + + /** + * @return int|null + */ + public function getSavedShareId():?int { + return $this->savedShareId; + } + + /** + * @return void + */ + public function resetLastPublicShareData():void { + $this->lastPublicShareData = null; + $this->lastPublicShareId = null; + $this->userWhoCreatedLastPublicShare = null; + } + + /** + * @param string $user + * + * @return void + */ + public function resetLastShareInfoForUser(string $user):void { + if (isset($this->lastShareDataByUser[$user])) { + unset($this->lastShareDataByUser[$user]); + } + if (isset($this->lastShareIdByUser[$user])) { + unset($this->lastShareIdByUser[$user]); + } + } + + /** + * @return int + */ + public function getLocalLastShareTime():int { + return $this->localLastShareTime; + } + + /** + * @return int + */ + public function getServerLastShareTime():int { + return (int) $this->getLastShareData()->data->stime; + } + + /** + * @param string|null $postfix string to append to the end of the path + * + * @return string + */ + public function getSharesEndpointPath(?string $postfix = ''):string { + return "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares$postfix"; + } + + /** + * Split given permissions string each separated with "," into array of strings + * + * @param string $str + * + * @return string[] + */ + private function splitPermissionsString(string $str):array { + $str = \trim($str); + $permissions = \array_map('trim', \explode(',', $str)); + + /* We use 'all', 'uploadwriteonly' and 'change' in feature files + for readability. Parse into appropriate permissions and return them + without any duplications.*/ + if (\in_array('all', $permissions, true)) { + $permissions = \array_keys(SharingHelper::PERMISSION_TYPES); + } + if (\in_array('uploadwriteonly', $permissions, true)) { + // remove 'uploadwriteonly' from $permissions + $permissions = \array_diff($permissions, ['uploadwriteonly']); + $permissions = \array_merge($permissions, ['create']); + } + if (\in_array('change', $permissions, true)) { + // remove 'change' from $permissions + $permissions = \array_diff($permissions, ['change']); + $permissions = \array_merge( + $permissions, + ['create', 'delete', 'read', 'update'] + ); + } + + return \array_unique($permissions); + } + + /** + * + * @return int + * + * @throws Exception + */ + public function getServerShareTimeFromLastResponse():int { + $stime = $this->getResponseXml(null, __METHOD__)->xpath("//stime"); + if ((bool) $stime) { + return (int) $stime[0]; + } + throw new Exception("Last share time (i.e. 'stime') could not be found in the response."); + } + + /** + * @return void + */ + private function waitToCreateShare():void { + if (($this->localLastShareTime !== null) + && ((\microtime(true) - $this->localLastShareTime) < 1) + ) { + // prevent creating two shares with the same "stime" which is + // based on seconds, this affects share merging order and could + // affect expected test result order + \sleep(1); + } + } + + /** + * @param string $user + * @param TableNode|null $body + * TableNode $body should not have any heading and can have following rows | + * | path | The folder or file path to be shared | + * | name | A (human-readable) name for the share, | + * | | which can be up to 64 characters in length. | + * | publicUpload | Whether to allow public upload to a public | + * | | shared folder. Write true for allowing. | + * | password | The password to protect the public link share with. | + * | expireDate | An expire date for public link shares. | + * | | This argument takes a date string in any format | + * | | that can be passed to strtotime(), for example: | + * | | 'YYYY-MM-DD' or '+ x days'. It will be converted to | + * | | 'YYYY-MM-DD' format before sending | + * | expireDateAsString | An expire date string for public link shares. | + * | | Whatever string is provided will be sent as the | + * | | expire date. For example, use this to test sending | + * | | invalid date strings. | + * | permissions | The permissions to set on the share. | + * | | 1 = read; 2 = update; 4 = create; | + * | | 8 = delete; 16 = share; 31 = all | + * | | 15 = change | + * | | 4 = uploadwriteonly | + * | | (default: 31, for public shares: 1) | + * | | Pass either the (total) number, | + * | | or the keyword, | + * | | or an comma separated list of keywords | + * | shareWith | The user or group id with which the file should | + * | | be shared. | + * | shareType | The type of the share. This can be one of: | + * | | 0 = user, 1 = group, 3 = public_link, | + * | | 6 = federated (cloud share). | + * | | Pass either the number or the keyword. | + * + * @return void + * @throws Exception + */ + public function createShareWithSettings(string $user, ?TableNode $body):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeRows( + $body, + ['path'], + $this->shareFields + ); + $bodyRows = $body->getRowsHash(); + $bodyRows['name'] = \array_key_exists('name', $bodyRows) ? $bodyRows['name'] : null; + $bodyRows['shareWith'] = \array_key_exists('shareWith', $bodyRows) ? $bodyRows['shareWith'] : null; + $bodyRows['shareWith'] = $this->getActualUsername($bodyRows['shareWith']); + $bodyRows['publicUpload'] = \array_key_exists('publicUpload', $bodyRows) ? $bodyRows['publicUpload'] === 'true' : null; + $bodyRows['password'] = \array_key_exists('password', $bodyRows) ? $this->getActualPassword($bodyRows['password']) : null; + + if (\array_key_exists('permissions', $bodyRows)) { + if (\is_numeric($bodyRows['permissions'])) { + $bodyRows['permissions'] = (int) $bodyRows['permissions']; + } else { + $bodyRows['permissions'] = $this->splitPermissionsString($bodyRows['permissions']); + } + } else { + $bodyRows['permissions'] = null; + } + if (\array_key_exists('shareType', $bodyRows)) { + if (\is_numeric($bodyRows['shareType'])) { + $bodyRows['shareType'] = (int) $bodyRows['shareType']; + } + } else { + $bodyRows['shareType'] = null; + } + + Assert::assertFalse( + isset($bodyRows['expireDate'], $bodyRows['expireDateAsString']), + 'expireDate and expireDateAsString cannot be set at the same time.' + ); + $needToParse = \array_key_exists('expireDate', $bodyRows); + $expireDate = $bodyRows['expireDate'] ?? $bodyRows['expireDateAsString'] ?? null; + $bodyRows['expireDate'] = $needToParse ? \date('Y-m-d', \strtotime($expireDate)) : $expireDate; + $this->createShare( + $user, + $bodyRows['path'], + $bodyRows['shareType'], + $bodyRows['shareWith'], + $bodyRows['publicUpload'], + $bodyRows['password'], + $bodyRows['permissions'], + $bodyRows['name'], + $bodyRows['expireDate'] + ); + } + + /** + * @Given auto-accept shares has been disabled + * + * @return void + */ + public function autoAcceptSharesHasBeenDisabled():void { + if (OcisHelper::isTestingOnOcisOrReva()) { + // auto-accept shares is disabled by default on OCIS. + // so there is nothing to do, just return + return; + } + + $this->appConfigurationContext->serverParameterHasBeenSetTo( + "shareapi_auto_accept_share", + "core", + "no" + ); + } + + /** + * @When /^user "([^"]*)" creates a share using the sharing API with settings$/ + * + * @param string $user + * @param TableNode|null $body {@link createShareWithSettings} + * + * @return void + * @throws Exception + */ + public function userCreatesAShareWithSettings(string $user, ?TableNode $body):void { + $user = $this->getActualUsername($user); + $this->createShareWithSettings( + $user, + $body + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has created a share with settings$/ + * + * @param string $user + * @param TableNode|null $body {@link createShareWithSettings} + * + * @return void + * @throws Exception + */ + public function userHasCreatedAShareWithSettings(string $user, ?TableNode $body) { + $this->createShareWithSettings( + $user, + $body + ); + $this->theHTTPStatusCodeShouldBe( + 200, + "Failed HTTP status code for last share for user $user" . ", Reason: " . $this->getResponse()->getReasonPhrase() + ); + } + + /** + * @When /^the user creates a share using the sharing API with settings$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserCreatesAShareWithSettings(?TableNode $body):void { + $this->createShareWithSettings($this->currentUser, $body); + } + + /** + * @When /^user "([^"]*)" creates a public link share using the sharing API with settings$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userCreatesAPublicLinkShareWithSettings(string $user, ?TableNode $body):void { + $rows = $body->getRows(); + // A public link share is shareType 3 + $rows[] = ['shareType', 'public_link']; + $newBody = new TableNode($rows); + $this->createShareWithSettings($user, $newBody); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has created a public link share with settings$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userHasCreatedAPublicLinkShareWithSettings(string $user, ?TableNode $body):void { + $this->userCreatesAPublicLinkShareWithSettings($user, $body); + $this->ocsContext->theOCSStatusCodeShouldBe("100,200"); + $this->theHTTPStatusCodeShouldBe(200); + $this->clearStatusCodeArrays(); + } + + /** + * @When /^the user creates a public link share using the sharing API with settings$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserCreatesAPublicLinkShareWithSettings(?TableNode $body):void { + $this->userCreatesAPublicLinkShareWithSettings($this->currentUser, $body); + } + + /** + * @Given /^the user has created a share with settings$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserHasCreatedAShareWithSettings(?TableNode $body):void { + $this->createShareWithSettings($this->currentUser, $body); + $this->ocsContext->theOCSStatusCodeShouldBe("100,200"); + $this->theHTTPStatusCodeShouldBe(200); + } + + /** + * @Given /^the user has created a public link share with settings$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserHasCreatedAPublicLinkShareWithSettings(?TableNode $body):void { + $this->theUserCreatesAPublicLinkShareWithSettings($body); + $this->ocsContext->theOCSStatusCodeShouldBe("100,200"); + $this->theHTTPStatusCodeShouldBe(200); + } + + /** + * @param string $user + * @param string $path + * @param boolean $publicUpload + * @param string|null $sharePassword + * @param string|int|string[]|int[]|null $permissions + * @param string|null $linkName + * @param string|null $expireDate + * + * @return void + */ + public function createAPublicShare( + string $user, + string $path, + bool $publicUpload = false, + string $sharePassword = null, + $permissions = null, + ?string $linkName = null, + ?string $expireDate = null + ):void { + $user = $this->getActualUsername($user); + $this->createShare( + $user, + $path, + 'public_link', + null, // shareWith + $publicUpload, + $sharePassword, + $permissions, + $linkName, + $expireDate + ); + } + + /** + * @When /^user "([^"]*)" creates a public link share of (?:file|folder) "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userCreatesAPublicLinkShareOf(string $user, string $path):void { + $this->createAPublicShare($user, $path); + } + + /** + * @Given /^user "([^"]*)" has created a public link share of (?:file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userHasCreatedAPublicLinkShareOf(string $user, string $path):void { + $this->createAPublicShare($user, $path); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $path + * + * @return void + */ + public function createPublicLinkShareOfResourceAsCurrentUser(string $path):void { + $this->createAPublicShare($this->currentUser, $path); + } + + /** + * @When /^the user creates a public link share of (?:file|folder) "([^"]*)" using the sharing API$/ + * + * @param string $path + * + * @return void + */ + public function aPublicLinkShareOfIsCreated(string $path):void { + $this->createPublicLinkShareOfResourceAsCurrentUser($path); + } + + /** + * @Given /^the user has created a public link share of (?:file|folder) "([^"]*)"$/ + * + * @param string $path + * + * @return void + */ + public function aPublicLinkShareOfHasCreated(string $path):void { + $this->createPublicLinkShareOfResourceAsCurrentUser($path); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function createPublicLinkShareOfResourceWithPermission( + string $user, + string $path, + $permissions + ):void { + $this->createAPublicShare($user, $path, true, null, $permissions); + } + + /** + * @When /^user "([^"]*)" creates a public link share of (?:file|folder) "([^"]*)" using the sharing API with (read|update|create|delete|change|uploadwriteonly|share|all) permission(?:s|)$/ + * + * @param string $user + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function userCreatesAPublicLinkShareOfWithPermission( + string $user, + string $path, + $permissions + ):void { + $this->createPublicLinkShareOfResourceWithPermission( + $user, + $path, + $permissions + ); + } + + /** + * @Given /^user "([^"]*)" has created a public link share of (?:file|folder) "([^"]*)" with (read|update|create|delete|change|uploadwriteonly|share|all) permission(?:s|)$/ + * + * @param string $user + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function userHasCreatedAPublicLinkShareOfWithPermission( + string $user, + string $path, + $permissions + ):void { + $this->createPublicLinkShareOfResourceWithPermission( + $user, + $path, + $permissions + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function createPublicLinkShareWithPermissionByCurrentUser(string $path, $permissions):void { + $this->createAPublicShare( + $this->currentUser, + $path, + true, + null, + $permissions + ); + } + + /** + * @When /^the user creates a public link share of (?:file|folder) "([^"]*)" using the sharing API with (read|update|create|delete|change|uploadwriteonly|share|all) permission(?:s|)$/ + * + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function aPublicLinkShareOfIsCreatedWithPermission(string $path, $permissions):void { + $this->createPublicLinkShareWithPermissionByCurrentUser($path, $permissions); + } + + /** + * @Given /^the user has created a public link share of (?:file|folder) "([^"]*)" with (read|update|create|delete|change|uploadwriteonly|share|all) permission(?:s|)$/ + * + * @param string $path + * @param string|int|string[]|int[]|null $permissions + * + * @return void + */ + public function aPublicLinkShareOfHasCreatedWithPermission(string $path, $permissions):void { + $this->createPublicLinkShareWithPermissionByCurrentUser($path, $permissions); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function createPublicLinkShareOfResourceWithExpiry( + string $user, + string $path, + string $expiryDate + ):void { + $this->createAPublicShare( + $user, + $path, + true, + null, + null, + null, + $expiryDate + ); + } + + /** + * @When /^user "([^"]*)" creates a public link share of (?:file|folder) "([^"]*)" using the sharing API with expiry "([^"]*)"$/ + * + * @param string $user + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function userCreatesAPublicLinkShareOfWithExpiry( + string $user, + string $path, + string $expiryDate + ):void { + $this->createPublicLinkShareOfResourceWithExpiry( + $user, + $path, + $expiryDate + ); + } + + /** + * @Given /^user "([^"]*)" has created a public link share of (?:file|folder) "([^"]*)" with expiry "([^"]*)"$/ + * + * @param string $user + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function userHasCreatedAPublicLinkShareOfWithExpiry( + string $user, + string $path, + string $expiryDate + ):void { + $this->createPublicLinkShareOfResourceWithExpiry( + $user, + $path, + $expiryDate + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function createPublicLinkShareOfResourceWithExpiryByCurrentUser( + string $path, + string $expiryDate + ):void { + $this->createAPublicShare( + $this->currentUser, + $path, + true, + null, + null, + null, + $expiryDate + ); + } + + /** + * @When /^the user creates a public link share of (?:file|folder) "([^"]*)" using the sharing API with expiry "([^"]*)$"/ + * + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function aPublicLinkShareOfIsCreatedWithExpiry( + string $path, + string $expiryDate + ):void { + $this->createPublicLinkShareOfResourceWithExpiryByCurrentUser( + $path, + $expiryDate + ); + } + + /** + * @Given /^the user has created a public link share of (?:file|folder) "([^"]*)" with expiry "([^"]*)$/ + * + * @param string $path + * @param string $expiryDate in a valid date format, e.g. "+30 days" + * + * @return void + */ + public function aPublicLinkShareOfHasCreatedWithExpiry( + string $path, + string $expiryDate + ):void { + $this->createPublicLinkShareOfResourceWithExpiryByCurrentUser( + $path, + $expiryDate + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * Give the mimetype of the last shared file + * + * @return string + */ + public function getMimeTypeOfLastSharedFile():string { + return \json_decode(\json_encode($this->getLastShareData()->data->mimetype), true)[0]; + } + + /** + * @param string $url + * @param string|null $user + * @param string|null $password + * @param string|null $mimeType + * + * @return void + */ + private function checkDownload( + string $url, + ?string $user = null, + ?string $password = null, + ?string $mimeType = null + ) { + $password = $this->getActualPassword($password); + $headers = ['X-Requested-With' => 'XMLHttpRequest']; + $this->response = HttpRequestHelper::get( + $url, + $this->getStepLineRef(), + $user, + $password, + $headers + ); + Assert::assertEquals( + 200, + $this->response->getStatusCode(), + __METHOD__ + . " Expected status code is '200' but got '" + . $this->response->getStatusCode() + . "'" + ); + + $buf = ''; + $body = $this->response->getBody(); + while (!$body->eof()) { + // read everything + $buf .= $body->read(8192); + } + $body->close(); + + if ($mimeType !== null) { + $finfo = new finfo; + Assert::assertEquals( + $mimeType, + $finfo->buffer($buf, FILEINFO_MIME_TYPE), + __METHOD__ + . " Expected mimeType '$mimeType' but got '" + . $finfo->buffer($buf, FILEINFO_MIME_TYPE) + ); + } + } + + /** + * @Then /^user "([^"]*)" should not be able to create a public link share of (file|folder) "([^"]*)" using the sharing API$/ + * + * @param string $sharer + * @param string $entry + * @param string $filepath + * + * @return void + * @throws Exception + */ + public function shouldNotBeAbleToCreatePublicLinkShare(string $sharer, string $entry, string $filepath):void { + $this->asFileOrFolderShouldExist( + $this->getActualUsername($sharer), + $entry, + $filepath + ); + $this->createAPublicShare($sharer, $filepath); + Assert::assertEquals( + 404, + $this->ocsContext->getOCSResponseStatusCode($this->response), + __METHOD__ + . " Expected response status code is '404' but got '" + . $this->ocsContext->getOCSResponseStatusCode($this->response) + . "'" + ); + } + + /** + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function updateLastShareByCurrentUser(?TableNode $body):void { + $this->updateLastShareWithSettings($this->currentUser, $body); + } + + /** + * @When /^the user updates the last share using the sharing API with$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserUpdatesTheLastShareWith(?TableNode $body):void { + $this->updateLastShareByCurrentUser($body); + } + + /** + * @Given /^the user has updated the last share with$/ + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theUserHasUpdatedTheLastShareWith(?TableNode $body):void { + $this->updateLastShareByCurrentUser($body); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param TableNode|null $body + * @param string|null $shareOwner + * @param bool $updateLastPublicLink + * + * @return void + * @throws Exception + */ + public function updateLastShareWithSettings( + string $user, + ?TableNode $body, + ?string $shareOwner = null, + ?bool $updateLastPublicLink = false + ):void { + $user = $this->getActualUsername($user); + + if ($updateLastPublicLink) { + $share_id = $this->getLastPublicLinkShareId(); + } else { + if ($shareOwner === null) { + $share_id = $this->getLastShareId(); + } else { + $share_id = $this->getLastShareIdForUser($shareOwner); + } + } + + $this->verifyTableNodeRows( + $body, + [], + $this->shareFields + ); + $bodyRows = $body->getRowsHash(); + if (\array_key_exists('expireDate', $bodyRows)) { + $dateModification = $bodyRows['expireDate']; + if (!empty($bodyRows['expireDate'])) { + $bodyRows['expireDate'] = \date('Y-m-d', \strtotime($dateModification)); + } else { + $bodyRows['expireDate'] = ''; + } + } + if (\array_key_exists('password', $bodyRows)) { + $bodyRows['password'] = $this->getActualPassword($bodyRows['password']); + } + if (\array_key_exists('permissions', $bodyRows)) { + if (\is_numeric($bodyRows['permissions'])) { + $bodyRows['permissions'] = (int) $bodyRows['permissions']; + } else { + $bodyRows['permissions'] = $this->splitPermissionsString($bodyRows['permissions']); + $bodyRows['permissions'] = SharingHelper::getPermissionSum($bodyRows['permissions']); + } + } + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "PUT", + $this->getSharesEndpointPath("/$share_id"), + $this->getStepLineRef(), + $bodyRows, + $this->ocsApiVersion + ); + } + + /** + * @When /^user "([^"]*)" updates the last share using the sharing API with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userUpdatesTheLastShareWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" updates the last public link share using the sharing API with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userUpdatesTheLastPublicLinkShareWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body, null, true); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has updated the last share with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userHasUpdatedTheLastShareWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Given /^user "([^"]*)" has updated the last public link share with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userHasUpdatedTheLastPublicLinkShareWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body, null, true); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Given /^user "([^"]*)" has updated the last share of "([^"]*)" with$/ + * + * @param string $user + * @param string $shareOwner + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userHasUpdatedTheLastShareOfWith(string $user, string $shareOwner, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body, $shareOwner); + $this->theHTTPStatusCodeShouldBeSuccess(); + if ($this->ocsApiVersion == 1) { + $this->ocsContext->theOCSStatusCodeShouldBe("100"); + } elseif ($this->ocsApiVersion === 2) { + $this->ocsContext->theOCSStatusCodeShouldBe("200"); + } else { + throw new Exception('Invalid ocs api version used'); + } + } + + /** + * @param string $name + * @param string $url + * @param string $path + * + * @return void + */ + public function addToListOfCreatedPublicLinks(string $name, string $url, string $path = ""):void { + $this->createdPublicLinks[] = ["name" => $name, "url" => $url, "path" => $path]; + } + + /** + * @param string $user + * @param string|null $path + * @param string|null $shareType + * @param string|null $shareWith + * @param bool|null $publicUpload + * @param string|null $sharePassword + * @param string|int|string[]|int[]|null $permissions + * @param string|null $linkName + * @param string|null $expireDate + * @param string $sharingApp + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function createShare( + string $user, + ?string $path = null, + ?string $shareType = null, + ?string $shareWith = null, + ?bool $publicUpload = null, + ?string $sharePassword = null, + $permissions = null, + ?string $linkName = null, + ?string $expireDate = null, + string $sharingApp = 'files_sharing' + ):void { + $userActual = $this->getActualUsername($user); + if (\is_string($permissions) && !\is_numeric($permissions)) { + $permissions = $this->splitPermissionsString($permissions); + } + $this->waitToCreateShare(); + $this->response = SharingHelper::createShare( + $this->getBaseUrl(), + $userActual, + $this->getPasswordForUser($user), + $path, + $shareType, + $this->getStepLineRef(), + $shareWith, + $publicUpload, + $sharePassword, + $permissions, + $linkName, + $expireDate, + $this->ocsApiVersion, + $this->sharingApiVersion, + $sharingApp + ); + $httpStatusCode = $this->response->getStatusCode(); + // In case of HTTP status code 204 "no content", or a failure code like 4xx + // in the HTTP or OCS status there is no useful content in response payload body. + // Clear the test-runner's memory of "last share data" to avoid later steps + // accidentally using some previous share data. + if (($httpStatusCode === 204) + || !$this->theHTTPStatusCodeWasSuccess() + || (($httpStatusCode === 200) && ($this->ocsContext->getOCSResponseStatusCode($this->response) > 299)) + ) { + if ($shareType === 'public_link') { + $this->resetLastPublicShareData(); + } else { + $this->resetLastShareInfoForUser($user); + } + } else { + if ($shareType === 'public_link') { + $this->setLastPublicShareData($this->getResponseXml(null, __METHOD__)); + $this->setLastPublicLinkShareId((string) $this->lastPublicShareData->data[0]->id); + $this->setUserWhoCreatedLastPublicShare($user); + if (isset($this->lastPublicShareData->data)) { + $linkName = (string) $this->lastPublicShareData->data[0]->name; + $linkUrl = (string) $this->lastPublicShareData->data[0]->url; + $this->addToListOfCreatedPublicLinks($linkName, $linkUrl, $path); + } + } else { + $shareData = $this->getResponseXml(null, __METHOD__); + $this->lastShareDataByUser[$user] = $shareData; + $shareId = (string) $shareData->data[0]->id; + $this->setLastShareIdOf($user, $shareId); + } + } + $this->localLastShareTime = \microtime(true); + } + + /** + * @param string $field + * @param string $value + * @param string $contentExpected + * @param bool $expectSuccess if true then the caller expects that the field + * has the expected content + * emit debugging information if the field is not as expected + * + * @return bool + */ + public function doesFieldValueMatchExpectedContent( + string $field, + string $value, + string $contentExpected, + bool $expectSuccess = true + ):bool { + if (($contentExpected === "ANY_VALUE") + || (($contentExpected === "A_TOKEN") && (\strlen($value) === 15)) + || (($contentExpected === "A_NUMBER") && \is_numeric($value)) + || (($contentExpected === "A_STRING") && \is_string($value) && $value !== "") + || (($contentExpected === "AN_URL") && $this->isAPublicLinkUrl($value)) + || (($field === 'remote') && (\rtrim($value, "/") === $contentExpected)) + || ($contentExpected === $value) + ) { + if (!$expectSuccess) { + echo $field . " is unexpectedly set with value '" . $value . "'\n"; + } + return true; + } + return false; + } + + /** + * @param string $field + * @param string|null $contentExpected + * @param bool $expectSuccess if true then the caller expects that the field + * is in the response with the expected content + * so emit debugging information if the field is not correct + * @param SimpleXMLElement|null $data + * + * @return bool + * @throws Exception + */ + public function isFieldInResponse(string $field, ?string $contentExpected, bool $expectSuccess = true, ?SimpleXMLElement $data = null):bool { + if ($data === null) { + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + } + Assert::assertIsObject($data, __METHOD__ . " data not found in response XML"); + + $dateFieldsArrayToConvert = ['expiration', 'original_date', 'new_date']; + //do not try to convert empty date + if ((string) \in_array($field, \array_merge($dateFieldsArrayToConvert)) && !empty($contentExpected)) { + $timestamp = \strtotime($contentExpected, $this->getServerShareTimeFromLastResponse()); + // strtotime returns false if it failed to parse, just leave it as it is in that condition + if ($timestamp !== false) { + $contentExpected + = \date( + 'Y-m-d', + $timestamp + ) . " 00:00:00"; + } + } + $contentExpected = (string) $contentExpected; + + if (\count($data->element) > 0) { + $fieldIsSet = false; + $value = ""; + foreach ($data as $element) { + if (isset($element->$field)) { + $fieldIsSet = true; + $value = (string) $element->$field; + if ($this->doesFieldValueMatchExpectedContent( + $field, + $value, + $contentExpected, + $expectSuccess + ) + ) { + return true; + } + } + } + } else { + $fieldIsSet = isset($data->$field); + if ($fieldIsSet) { + $value = (string) $data->$field; + if ($this->doesFieldValueMatchExpectedContent( + $field, + $value, + $contentExpected, + $expectSuccess + ) + ) { + return true; + } + } + } + if ($expectSuccess) { + if ($fieldIsSet) { + echo $field . " has unexpected value '" . $value . "'\n"; + } else { + echo $field . " is not set in response\n"; + } + } + return false; + } + + /** + * @Then no files or folders should be included in the response + * + * @return void + */ + public function checkNoFilesFoldersInResponse():void { + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + Assert::assertIsObject($data, __METHOD__ . " data not found in response XML"); + Assert::assertCount(0, $data); + } + + /** + * @Then exactly :count file/files or folder/folders should be included in the response + * + * @param string $count + * + * @return void + */ + public function checkCountFilesFoldersInResponse(string $count):void { + $count = (int) $count; + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + Assert::assertIsObject($data, __METHOD__ . " data not found in response XML"); + Assert::assertCount($count, $data, __METHOD__ . " the response does not contain $count entries"); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" should be included in the response$/ + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function checkSharedFileInResponse(string $filename):void { + $filename = "/" . \ltrim($filename, '/'); + Assert::assertTrue( + $this->isFieldInResponse('file_target', "$filename"), + "'file_target' value '$filename' was not found in response" + ); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" should not be included in the response$/ + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function checkSharedFileNotInResponse(string $filename):void { + $filename = "/" . \ltrim($filename, '/'); + Assert::assertFalse( + $this->isFieldInResponse('file_target', "$filename", false), + "'file_target' value '$filename' was unexpectedly found in response" + ); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" should be included as path in the response$/ + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function checkSharedFileAsPathInResponse(string $filename):void { + $filename = "/" . \ltrim($filename, '/'); + Assert::assertTrue( + $this->isFieldInResponse('path', "$filename"), + "'path' value '$filename' was not found in response" + ); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" should not be included as path in the response$/ + * + * @param string $filename + * + * @return void + * @throws Exception + */ + public function checkSharedFileAsPathNotInResponse(string $filename):void { + $filename = "/" . \ltrim($filename, '/'); + Assert::assertFalse( + $this->isFieldInResponse('path', "$filename", false), + "'path' value '$filename' was unexpectedly found in response" + ); + } + + /** + * @Then /^(user|group) "([^"]*)" should be included in the response$/ + * + * @param string $type + * @param string $user + * + * @return void + * @throws Exception + */ + public function checkSharedUserOrGroupInResponse(string $type, string $user):void { + if ($type === 'user') { + $user = $this->getActualUsername($user); + } + Assert::assertTrue( + $this->isFieldInResponse('share_with', "$user"), + "'share_with' value '$user' was not found in response" + ); + } + + /** + * @Then /^user "([^"]*)" should not be included in the response$/ + * @Then /^group "([^"]*)" should not be included in the response$/ + * + * @param string $userOrGroup + * + * @return void + * @throws Exception + */ + public function checkSharedUserOrGroupNotInResponse(string $userOrGroup):void { + Assert::assertFalse( + $this->isFieldInResponse('share_with', "$userOrGroup", false), + "'share_with' value '$userOrGroup' was unexpectedly found in response" + ); + } + + /** + * @param string $userOrGroupId + * @param string|int $shareType 0 or "user" for user, 1 or "group" for group + * @param string|int|string[]|int[]|null $permissions + * + * @return bool + */ + public function isUserOrGroupInSharedData(string $userOrGroupId, $shareType, $permissions = null):bool { + $shareType = SharingHelper::getShareType($shareType); + + if ($permissions !== null) { + if (\is_string($permissions) && !\is_numeric($permissions)) { + $permissions = $this->splitPermissionsString($permissions); + } + $permissionSum = SharingHelper::getPermissionSum($permissions); + } + + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + if (\is_iterable($data)) { + foreach ($data as $element) { + if (($element->share_type->__toString() === (string) $shareType) + && ($element->share_with->__toString() === $userOrGroupId) + && ($permissions === null || $permissionSum === (int) $element->permissions->__toString()) + ) { + return true; + } + } + return false; + } + \error_log( + "INFORMATION: isUserOrGroupInSharedData response XML data is " . + \gettype($data) . + " and therefore does not contain share_with information." + ); + return false; + } + + /** + * + * @param string $user1 + * @param string $filepath + * @param string $user2 + * @param string|int|string[]|int[] $permissions + * @param bool|null $getShareData If true then only create the share if it is not + * already existing, and at the end request the + * share information and leave that in $this->response + * Typically used in a "Given" step which verifies + * that the share did get created successfully. + * + * @return void + */ + public function shareFileWithUserUsingTheSharingApi( + string $user1, + string $filepath, + string $user2, + $permissions = null, + ?bool $getShareData = false + ):void { + $user1Actual = $this->getActualUsername($user1); + $user2Actual = $this->getActualUsername($user2); + + $path = $this->getSharesEndpointPath("?path=" . \urlencode($filepath)); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user1Actual, + $this->getPasswordForUser($user1), + "GET", + $path, + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + if ($getShareData && $this->isUserOrGroupInSharedData($user2Actual, "user", $permissions)) { + return; + } else { + $this->createShare( + $user1, + $filepath, + '0', + $user2Actual, + null, + null, + $permissions + ); + } + if ($getShareData) { + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user1Actual, + $this->getPasswordForUser($user1), + "GET", + $path, + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + } + + /** + * @When /^user "([^"]*)" shares (?:file|folder|entry) "([^"]*)" with user "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @When /^user "([^"]*)" shares (?:file|folder|entry) "([^"]*)" with user "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $user1 + * @param string $filepath + * @param string $user2 + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function userSharesFileWithUserUsingTheSharingApi( + string $user1, + string $filepath, + string $user2, + $permissions = null + ):void { + $this->shareFileWithUserUsingTheSharingApi( + $user1, + $filepath, + $user2, + $permissions + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" shares the following (?:files|folders|entries) with user "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @When /^user "([^"]*)" shares the following (?:files|folders|entries) with user "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $sharer + * @param string $sharee + * @param TableNode $table + * @param string|int|string[]|int[] $permissions + * + * @return void + * @throws Exception + */ + public function userSharesTheFollowingFilesWithUserUsingTheSharingApi( + string $sharer, + string $sharee, + TableNode $table, + $permissions = null + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $filepath) { + $this->userSharesFileWithUserUsingTheSharingApi( + $sharer, + $filepath["path"], + $sharee, + $permissions + ); + } + } + + /** + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with user "([^"]*)"(?: with permissions (\d+))?$/ + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with user "([^"]*)" with permissions "([^"]*)"$/ + * + * @param string $user1 + * @param string $filepath + * @param string $user2 + * @param string|int|string[]|int[] $permissions + * + * @return void + * @throws Exception + */ + public function userHasSharedFileWithUserUsingTheSharingApi( + string $user1, + string $filepath, + string $user2, + $permissions = null + ):void { + $user1 = $this->getActualUsername($user1); + $user2 = $this->getActualUsername($user2); + $this->shareFileWithUserUsingTheSharingApi( + $user1, + $filepath, + $user2, + $permissions, + true + ); + $this->ocsContext->assertOCSResponseIndicatesSuccess( + 'The ocs share response does not indicate success.', + ); + // this is expected to fail if a file is shared with create and delete permissions, which is not possible + Assert::assertTrue( + $this->isUserOrGroupInSharedData($user2, "user", $permissions), + __METHOD__ . " User $user1 failed to share $filepath with user $user2" + ); + } + + /** + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with the administrator(?: with permissions (\d+))?$/ + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with the administrator with permissions "([^"]*)"$/ + * + * @param string $sharer + * @param string $filepath + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function userHasSharedFileWithTheAdministrator( + string $sharer, + string $filepath, + $permissions = null + ):void { + $admin = $this->getAdminUsername(); + $this->userHasSharedFileWithUserUsingTheSharingApi( + $sharer, + $filepath, + $admin, + $permissions + ); + } + + /** + * @When /^the user shares (?:file|folder|entry) "([^"]*)" with user "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @When /^the user shares (?:file|folder|entry) "([^"]*)" with user "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $filepath + * @param string $user2 + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function theUserSharesFileWithUserUsingTheSharingApi( + string $filepath, + string $user2, + $permissions = null + ) { + $this->userSharesFileWithUserUsingTheSharingApi( + $this->getCurrentUser(), + $filepath, + $user2, + $permissions + ); + } + + /** + * @Given /^the user has shared (?:file|folder|entry) "([^"]*)" with user "([^"]*)"(?: with permissions (\d+))?$/ + * @Given /^the user has shared (?:file|folder|entry) "([^"]*)" with user "([^"]*)" with permissions "([^"]*)"$/ + * + * @param string $filepath + * @param string $user2 + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function theUserHasSharedFileWithUserUsingTheSharingApi( + string $filepath, + string $user2, + $permissions = null + ):void { + $user2 = $this->getActualUsername($user2); + $this->userHasSharedFileWithUserUsingTheSharingApi( + $this->getCurrentUser(), + $filepath, + $user2, + $permissions + ); + } + + /** + * @When /^the user shares (?:file|folder|entry) "([^"]*)" with group "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @When /^the user shares (?:file|folder|entry) "([^"]*)" with group "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $filepath + * @param string $group + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function theUserSharesFileWithGroupUsingTheSharingApi( + string $filepath, + string $group, + $permissions = null + ):void { + $this->userSharesFileWithGroupUsingTheSharingApi( + $this->currentUser, + $filepath, + $group, + $permissions + ); + } + + /** + * @Given /^the user has shared (?:file|folder|entry) "([^"]*)" with group "([^"]*)"(?: with permissions (\d+))?$/ + * @Given /^the user has shared (?:file|folder|entry) "([^"]*)" with group "([^"]*)" with permissions "([^"]*)"$/ + * + * @param string $filepath + * @param string $group + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function theUserHasSharedFileWithGroupUsingTheSharingApi( + string $filepath, + string $group, + $permissions = null + ):void { + $this->userHasSharedFileWithGroupUsingTheSharingApi( + $this->currentUser, + $filepath, + $group, + $permissions + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * + * @param string $user + * @param string $filepath + * @param string $group + * @param string|int|string[]|int[] $permissions + * @param bool $getShareData If true then only create the share if it is not + * already existing, and at the end request the + * share information and leave that in $this->response + * Typically used in a "Given" step which verifies + * that the share did get created successfully. + * + * @return void + */ + public function shareFileWithGroupUsingTheSharingApi( + string $user, + string $filepath, + string$group, + $permissions = null, + bool $getShareData = false + ):void { + $userActual = $this->getActualUsername($user); + $path = $this->getSharesEndpointPath("?path=$filepath"); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $userActual, + $this->getPasswordForUser($user), + "GET", + $path, + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + if ($getShareData && $this->isUserOrGroupInSharedData($group, "group", $permissions)) { + return; + } else { + $this->createShare( + $user, + $filepath, + '1', + $group, + null, + null, + $permissions + ); + } + if ($getShareData) { + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $userActual, + $this->getPasswordForUser($user), + "GET", + $path, + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + } + + /** + * @When /^user "([^"]*)" shares (?:file|folder|entry) "([^"]*)" with group "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * @When /^user "([^"]*)" shares (?:file|folder|entry) "([^"]*)" with group "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * + * @param string $user + * @param string $filepath + * @param string $group + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function userSharesFileWithGroupUsingTheSharingApi( + string $user, + string $filepath, + string $group, + $permissions = null + ) { + $this->shareFileWithGroupUsingTheSharingApi( + $user, + $filepath, + $group, + $permissions + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" shares the following (?:files|folders|entries) with group "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @When /^user "([^"]*)" shares the following (?:files|folders|entries) with group "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $group + * @param TableNode $table + * @param string|int|string[]|int[] $permissions + * + * @return void + * @throws Exception + */ + public function userSharesTheFollowingFilesWithGroupUsingTheSharingApi( + string $user, + string $group, + TableNode $table, + $permissions = null + ) { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $filepath) { + $this->userSharesFileWithGroupUsingTheSharingApi( + $user, + $filepath["path"], + $group, + $permissions + ); + } + } + + /** + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with group "([^"]*)" with permissions "([^"]*)"$/ + * @Given /^user "([^"]*)" has shared (?:file|folder|entry) "([^"]*)" with group "([^"]*)"(?: with permissions (\d+))?$/ + * + * @param string $user + * @param string $filepath + * @param string $group + * @param string|int|string[]|int[] $permissions + * + * @return void + */ + public function userHasSharedFileWithGroupUsingTheSharingApi( + string $user, + string $filepath, + string $group, + $permissions = null + ) { + $this->shareFileWithGroupUsingTheSharingApi( + $user, + $filepath, + $group, + $permissions, + true + ); + + Assert::assertEquals( + true, + $this->isUserOrGroupInSharedData($group, "group", $permissions), + __METHOD__ + . " Could not assert that user '$user' has shared '$filepath' with group '$group' with permissions '$permissions'" + ); + } + + /** + * @When /^user "([^"]*)" tries to update the last share using the sharing API with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userTriesToUpdateTheLastShareUsingTheSharingApiWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body); + } + + /** + * @When /^user "([^"]*)" tries to update the last public link share using the sharing API with$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function userTriesToUpdateTheLastPublicLinkShareUsingTheSharingApiWith(string $user, ?TableNode $body):void { + $this->updateLastShareWithSettings($user, $body, null, true); + } + + /** + * @Then /^user "([^"]*)" should not be able to share (file|folder|entry) "([^"]*)" with (user|group) "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @Then /^user "([^"]*)" should not be able to share (file|folder|entry) "([^"]*)" with (user|group) "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $sharer + * @param string $entry + * @param string $filepath + * @param string $userOrGroupShareType + * @param string $sharee + * @param string|int|string[]|int[] $permissions + * + * @return void + * @throws Exception + */ + public function userTriesToShareFileUsingTheSharingApi( + string $sharer, + string $entry, + string $filepath, + string $userOrGroupShareType, + string $sharee, + $permissions = null + ):void { + $sharee = $this->getActualUsername($sharee); + $this->asFileOrFolderShouldExist( + $this->getActualUsername($sharer), + $entry, + $filepath + ); + $this->createShare( + $sharer, + $filepath, + $userOrGroupShareType, + $sharee, + null, + null, + $permissions + ); + $statusCode = $this->ocsContext->getOCSResponseStatusCode($this->response); + Assert::assertTrue( + ($statusCode == 404) || ($statusCode == 403), + "Sharing should have failed with status code 403 or 404 but got status code $statusCode" + ); + } + + /** + * @Then /^user "([^"]*)" should be able to share (file|folder|entry) "([^"]*)" with (user|group) "([^"]*)"(?: with permissions (\d+))? using the sharing API$/ + * @Then /^user "([^"]*)" should be able to share (file|folder|entry) "([^"]*)" with (user|group) "([^"]*)" with permissions "([^"]*)" using the sharing API$/ + * + * @param string $sharer + * @param string $entry + * @param string $filepath + * @param string $userOrGroupShareType + * @param string $sharee + * @param string|int|string[]|int[] $permissions + * + * @return void + * @throws Exception + */ + public function userShouldBeAbleToShareUsingTheSharingApi( + string $sharer, + string $entry, + string $filepath, + string $userOrGroupShareType, + string $sharee, + $permissions = null + ):void { + $sharee = $this->getActualUsername($sharee); + $this->asFileOrFolderShouldExist($sharer, $entry, $filepath); + $this->createShare( + $sharer, + $filepath, + $userOrGroupShareType, + $sharee, + null, + null, + $permissions + ); + + //v1.php returns 100 as success code + //v2.php returns 200 in the same case + $this->ocsContext->theOCSStatusCodeShouldBe("100, 200"); + } + + /** + * @When /^the user deletes the last share using the sharing API$/ + * + * @return void + */ + public function theUserDeletesLastShareUsingTheSharingAPI():void { + $this->deleteLastShareUsingSharingApiByCurrentUser(); + } + + /** + * @Given /^the user has deleted the last share$/ + * + * @return void + */ + public function theUserHasDeletedLastShareUsingTheSharingAPI():void { + $this->deleteLastShareUsingSharingApiByCurrentUser(); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user the user who will do the delete request + * @param string|null $sharer the specific user whose share will be deleted (if specified) + * @param bool $deleteLastPublicLink + * + * @return void + */ + public function deleteLastShareUsingSharingApi(string $user, string $sharer = null, bool $deleteLastPublicLink = false):void { + $user = $this->getActualUsername($user); + if ($deleteLastPublicLink) { + $shareId = $this->getLastPublicLinkShareId(); + } else { + if ($sharer === null) { + $shareId = $this->getLastShareId(); + } else { + $shareId = $this->getLastShareIdForUser($sharer); + } + } + $url = $this->getSharesEndpointPath("/$shareId"); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "DELETE", + $url, + null + ); + } + + /** + * @return void + */ + public function deleteLastShareUsingSharingApiByCurrentUser():void { + $this->deleteLastShareUsingSharingApi($this->currentUser); + } + + /** + * @When /^user "([^"]*)" deletes the last share using the sharing API$/ + * @When /^user "([^"]*)" tries to delete the last share using the sharing API$/ + * + * @param string $user + * + * @return void + */ + public function userDeletesLastShareUsingTheSharingApi(string $user):void { + $this->deleteLastShareUsingSharingApi($user); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" deletes the last public link share using the sharing API$/ + * @When /^user "([^"]*)" tries to delete the last public link share using the sharing API$/ + * + * @param string $user + * + * @return void + */ + public function userDeletesLastPublicLinkShareUsingTheSharingApi(string $user):void { + $this->deleteLastShareUsingSharingApi($user, null, true); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" deletes the last share of user "([^"]*)" using the sharing API$/ + * @When /^user "([^"]*)" tries to delete the last share of user "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $sharer + * + * @return void + */ + public function userDeletesLastShareOfUserUsingTheSharingApi(string $user, string $sharer):void { + $this->deleteLastShareUsingSharingApi($user, $sharer); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has deleted the last share$/ + * + * @param string $user + * + * @return void + */ + public function userHasDeletedLastShareUsingTheSharingApi(string $user):void { + $this->deleteLastShareUsingSharingApi($user); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^the user gets the info of the last share using the sharing API$/ + * + * @return void + * @throws Exception + */ + public function theUserGetsInfoOfLastShareUsingTheSharingApi():void { + $this->userGetsInfoOfLastShareUsingTheSharingApi($this->currentUser); + } + + /** + * @When /^user "([^"]*)" gets the info of the last share in language "([^"]*)" using the sharing API$/ + * @When /^user "([^"]*)" gets the info of the last share using the sharing API$/ + * + * @param string $user username that requests the information (might not be the user that has initiated the share) + * @param string|null $language + * + * @return void + * @throws Exception + */ + public function userGetsInfoOfLastShareUsingTheSharingApi(string $user, ?string $language = null):void { + $shareId = $this->getLastShareId(); + $language = TranslationHelper::getLanguage($language); + $this->getShareData($user, $shareId, $language); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^the user gets the info of the last public link share using the sharing API$/ + * + * @return void + * @throws Exception + */ + public function theUserGetsInfoOfLastPublicLinkShareUsingTheSharingApi():void { + $this->userGetsInfoOfLastPublicLinkShareUsingTheSharingApi($this->getUserWhoCreatedLastPublicShare()); + } + + /** + * @When /^user "([^"]*)" gets the info of the last public link share in language "([^"]*)" using the sharing API$/ + * @When /^user "([^"]*)" gets the info of the last public link share using the sharing API$/ + * + * @param string $user username that requests the information (might not be the user that has initiated the share) + * @param string|null $language + * + * @return void + * @throws Exception + */ + public function userGetsInfoOfLastPublicLinkShareUsingTheSharingApi(string $user, ?string $language = null):void { + if ($this->lastPublicShareId !== null) { + $shareId = $this->lastPublicShareId; + } else { + throw new Exception( + __METHOD__ . " last public link share data was not found" + ); + } + $language = TranslationHelper::getLanguage($language); + $this->getShareData($user, $shareId, $language); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Then /^as "([^"]*)" the info about the last share by user "([^"]*)" with user "([^"]*)" should include$/ + * + * @param string $requestor + * @param string $sharer + * @param string $sharee + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function asLastShareInfoAboutUserSharingWithUserShouldInclude( + string $requestor, + string $sharer, + string $sharee, + TableNode $table + ) { + $this->userGetsInfoOfLastShareUsingTheSharingApi($requestor); + $this->ocsContext->assertOCSResponseIndicatesSuccess(); + $this->checkFieldsOfLastResponseToUser($sharer, $sharee, $table); + } + + /** + * @Then /^the info about the last share by user "([^"]*)" with (?:user|group) "([^"]*)" should include$/ + * + * @param string $sharer + * @param string $sharee + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theInfoAboutTheLastShareByUserWithUserShouldInclude( + string $sharer, + string $sharee, + TableNode $table + ):void { + $this->asLastShareInfoAboutUserSharingWithUserShouldInclude($sharer, $sharer, $sharee, $table); + } + + /** + * Sets the id of the last shared file + * + * @param string $user + * @param string $shareId + * + * @return void + */ + public function setLastShareIdOf(string $user, string $shareId):void { + $this->lastShareIdByUser[$user] = $shareId; + $this->userWhoCreatedLastShare = $user; + } + + /** + * Sets the id of the last public link shared file + * + * @param string $shareId + * + * @return void + */ + public function setLastPublicLinkShareId(string $shareId):void { + $this->lastPublicShareId = $shareId; + } + + /** + * Retrieves the id of the last public link shared file + * + * @return string|null + */ + public function getLastPublicLinkShareId():?string { + return $this->lastPublicShareId; + } + + /** + * Sets the user who created the last public link share + * + * @param string $user + * + * @return void + */ + public function setUserWhoCreatedLastPublicShare(string $user):void { + $this->userWhoCreatedLastPublicShare = $user; + } + + /** + * Gets the user who created the last public link share + * + * @return string|null + */ + public function getUserWhoCreatedLastPublicShare():?string { + return $this->userWhoCreatedLastPublicShare; + } + + /** + * Retrieves the id of the last shared file + * + * @return string|null + */ + public function getLastShareId():?string { + return $this->getLastShareIdForUser($this->userWhoCreatedLastShare); + } + + /** + * @param string $user + * + * @return string|null + */ + public function getLastShareIdForUser(string $user):?string { + if ($user === null) { + throw new Exception( + __METHOD__ . " user not specified. Probably no user or group shares have been created yet in the test scenario." + ); + } + if (isset($this->lastShareIdByUser[$user])) { + return $this->lastShareIdByUser[$user]; + } else { + throw new Exception(__METHOD__ . " last share id for user '$user' was not found"); + } + } + + /** + * Retrieves all the shares of the respective user + * + * @param string $user + * + * @return ResponseInterface + */ + public function getListOfShares(string $user):ResponseInterface { + $user = $this->getActualUsername($user); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath(), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + return $this->response; + } + + /** + * Get share data of specific share_id + * + * @param string $user + * @param string $share_id + * @param string|null $language + * + * @return void + */ + public function getShareData(string $user, string $share_id, ?string $language = null):void { + $user = $this->getActualUsername($user); + $url = $this->getSharesEndpointPath("/$share_id"); + $headers = []; + if ($language !== null) { + $headers['Accept-Language'] = $language; + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "GET", + $url, + null, + null, + $headers + ); + } + + /** + * @When user :user gets all the shares shared with him using the sharing API + * + * @param string $user + * + * @return void + */ + public function userGetsAllTheSharesSharedWithHimUsingTheSharingApi(string $user):void { + $user = $this->getActualUsername($user); + $url = "/apps/files_sharing/api/v1/shares?shared_with_me=true"; + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + 'GET', + $url, + null + ); + } + + /** + * @When /^user "([^"]*)" gets the (|pending)\s?(user|group|user and group|public link) shares shared with him using the sharing API$/ + * + * @param string $user + * @param string $pending + * @param string $shareType + * + * @return void + */ + public function userGetsFilteredSharesSharedWithHimUsingTheSharingApi(string $user, string $pending, string $shareType):void { + $user = $this->getActualUsername($user); + if ($pending === "pending") { + $pendingClause = "&state=" . SharingHelper::SHARE_STATES['pending']; + } else { + $pendingClause = ""; + } + if ($shareType === 'public link') { + $shareType = 'public_link'; + } + if ($shareType === 'user and group') { + $rawShareTypes = SharingHelper::SHARE_TYPES['user'] . "," . SharingHelper::SHARE_TYPES['group']; + } else { + $rawShareTypes = SharingHelper::SHARE_TYPES[$shareType]; + } + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + 'GET', + $this->getSharesEndpointPath( + "?shared_with_me=true" . $pendingClause . "&share_types=" . $rawShareTypes + ), + null + ); + } + + /** + * @When /^user "([^"]*)" gets all the shares shared with him that are received as (?:file|folder|entry) "([^"]*)" using the provisioning API$/ + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userGetsAllSharesSharedWithHimFromFileOrFolderUsingTheProvisioningApi(string $user, string $path):void { + $user = $this->getActualUsername($user); + $url = "/apps/files_sharing/api/" + . "v{$this->sharingApiVersion}/shares?shared_with_me=true&path=$path"; + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + 'GET', + $url, + null + ); + } + + /** + * @When user :user gets all shares shared by him using the sharing API + * + * @param string $user + * + * @return void + */ + public function userGetsAllSharesSharedByHimUsingTheSharingApi(string $user):void { + $user = $this->getActualUsername($user); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath(), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + + /** + * @When the administrator gets all shares shared by him using the sharing API + * + * @return void + */ + public function theAdministratorGetsAllSharesSharedByHimUsingTheSharingApi():void { + $this->userGetsAllSharesSharedByHimUsingTheSharingApi($this->getAdminUsername()); + } + + /** + * @When /^user "([^"]*)" gets the (user|group|user and group|public link) shares shared by him using the sharing API$/ + * + * @param string $user + * @param string $shareType + * + * @return void + */ + public function userGetsFilteredSharesSharedByHimUsingTheSharingApi(string $user, string $shareType):void { + $user = $this->getActualUsername($user); + if ($shareType === 'public link') { + $shareType = 'public_link'; + } + if ($shareType === 'user and group') { + $rawShareTypes = SharingHelper::SHARE_TYPES['user'] . "," . SharingHelper::SHARE_TYPES['group']; + } else { + $rawShareTypes = SharingHelper::SHARE_TYPES[$shareType]; + } + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?share_types=" . $rawShareTypes), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + + /** + * @When user :user gets all the shares from the file :path using the sharing API + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userGetsAllTheSharesFromTheFileUsingTheSharingApi(string $user, string $path):void { + $user = $this->getActualUsername($user); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?path=$path"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + + /** + * @When user :user gets all the shares with reshares from the file :path using the sharing API + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userGetsAllTheSharesWithResharesFromTheFileUsingTheSharingApi( + string $user, + string $path + ):void { + $userActual = $this->getActualUsername($user); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $userActual, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?reshares=true&path=$path"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + + /** + * @When user :user gets all the shares inside the folder :path using the sharing API + * + * @param string $user + * @param string $path + * + * @return void + */ + public function userGetsAllTheSharesInsideTheFolderUsingTheSharingApi(string $user, string $path):void { + $user = $this->getActualUsername($user); + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?path=$path&subfiles=true"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + } + + /** + * @Then /^the response when user "([^"]*)" gets the info of the last share should include$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theResponseWhenUserGetsInfoOfLastShareShouldInclude( + string $user, + ?TableNode $body + ):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeRows($body, [], $this->shareResponseFields); + $this->getShareData($user, (string)$this->getLastShareData()->data[0]->id); + $this->theHTTPStatusCodeShouldBe( + 200, + "Error getting info of last share for user $user" + ); + $this->ocsContext->assertOCSResponseIndicatesSuccess( + __METHOD__ . + ' Error getting info of last share for user $user\n' . + $this->ocsContext->getOCSResponseStatusMessage( + $this->getResponse() + ) . '"' + ); + $this->checkFields($user, $body); + } + + /** + * @Then /^the response when user "([^"]*)" gets the info of the last public link share should include$/ + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function theResponseWhenUserGetsInfoOfLastPublicLinkShareShouldInclude( + string $user, + ?TableNode $body + ):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeRows($body, [], $this->shareResponseFields); + $this->getShareData($user, (string)$this->getLastPublicLinkShareId()); + $this->theHTTPStatusCodeShouldBe( + 200, + "Error getting info of last public link share for user $user" + ); + $this->ocsContext->assertOCSResponseIndicatesSuccess( + __METHOD__ . + ' Error getting info of last public link share for user $user\n' . + $this->ocsContext->getOCSResponseStatusMessage( + $this->getResponse() + ) . '"' + ); + $this->checkFields($user, $body); + } + + /** + * @Then the information of the last share of user :user should include + * + * @param string $user + * @param TableNode $body + * + * @return void + * @throws Exception + */ + public function informationOfLastShareShouldInclude( + string $user, + TableNode $body + ):void { + $user = $this->getActualUsername($user); + $shareId = $this->getLastShareIdForUser($user); + $this->getShareData($user, $shareId); + $this->theHTTPStatusCodeShouldBe( + 200, + "Error getting info of last share for user $user with share id $shareId" + ); + $this->verifyTableNodeRows($body, [], $this->shareResponseFields); + $this->checkFields($user, $body); + } + + /** + * @Then /^the information for user "((?:[^']*)|(?:[^"]*))" about the received share of (file|folder) "((?:[^']*)|(?:[^"]*))" shared with a (user|group) should include$/ + * + * @param string $user + * @param string $fileOrFolder + * @param string $fileName + * @param string $type + * @param TableNode $body should provide share_type + * + * @return void + * @throws Exception + */ + public function theFieldsOfTheResponseForUserForResourceShouldInclude( + string $user, + string $fileOrFolder, + string $fileName, + string $type, + TableNode $body + ):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($body, 2); + $fileName = $fileName[0] === "/" ? $fileName : '/' . $fileName; + $data = $this->getAllSharesSharedWithUser($user); + Assert::assertNotEmpty($data, 'No shares found for ' . $user); + + $bodyRows = $body->getRowsHash(); + Assert::assertArrayHasKey('share_type', $bodyRows, 'share_type is not provided'); + $share_id = null; + foreach ($data as $share) { + if ($share['file_target'] === $fileName && $share['item_type'] === $fileOrFolder) { + if (($share['share_type'] === SharingHelper::getShareType($bodyRows['share_type'])) + ) { + $share_id = $share['id']; + } + } + } + + Assert::assertNotNull($share_id, "Could not find share id for " . $user); + + if (\array_key_exists('expiration', $bodyRows) && $bodyRows['expiration'] !== '') { + $bodyRows['expiration'] = \date('d-m-Y', \strtotime($bodyRows['expiration'])); + } + + $this->getShareData($user, $share_id); + foreach ($bodyRows as $field => $value) { + if ($type === "user" && \in_array($field, ["share_with"])) { + $value = $this->getActualUsername($value); + } + if (\in_array($field, ["uid_owner"])) { + $value = $this->getActualUsername($value); + } + $value = $this->replaceValuesFromTable($field, $value); + Assert::assertTrue( + $this->isFieldInResponse($field, $value), + "$field doesn't have value '$value'" + ); + } + } + + /** + * @Then /^the last share_id should be included in the response/ + * + * @return void + * @throws Exception + */ + public function checkingLastShareIDIsIncluded():void { + $shareId = $this->getLastShareId(); + if (!$this->isFieldInResponse('id', $shareId)) { + Assert::fail( + "Share id $shareId not found in response" + ); + } + } + + /** + * @Then /^the last share id should not be included in the response/ + * + * @return void + * @throws Exception + */ + public function checkLastShareIDIsNotIncluded():void { + $shareId = $this->getLastShareId(); + if ($this->isFieldInResponse('id', $shareId, false)) { + Assert::fail( + "Share id $shareId has been found in response" + ); + } + } + + /** + * @Then /^the last public link share id should not be included in the response/ + * + * @return void + * @throws Exception + */ + public function checkLastPublicLinkShareIDIsNotIncluded():void { + $shareId = $this->getLastPublicLinkShareId(); + if ($this->isFieldInResponse('id', $shareId, false)) { + Assert::fail( + "Public link share id $shareId has been found in response" + ); + } + } + + /** + * @Then /^the response should not contain any share ids/ + * + * @return void + */ + public function theResponseShouldNotContainAnyShareIds():void { + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + $fieldIsSet = false; + $receivedShareCount = 0; + + if (\count($data->element) > 0) { + foreach ($data as $element) { + if (isset($element->id)) { + $fieldIsSet = true; + $receivedShareCount += 1; + } + } + } else { + if (isset($data->id)) { + $fieldIsSet = true; + $receivedShareCount += 1; + } + } + Assert::assertFalse( + $fieldIsSet, + "response contains $receivedShareCount share ids but should not contain any share ids" + ); + } + + /** + * @Then user :user should not see the share id of the last share + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function userShouldNotSeeShareIdOfLastShare(string $user):void { + $this->userGetsAllTheSharesSharedWithHimUsingTheSharingApi($user); + $this->checkLastShareIDIsNotIncluded(); + } + + /** + * @Then user :user should not have any received shares + * + * @param string $user + * + * @return void + */ + public function userShouldNotHaveAnyReceivedShares(string $user):void { + $this->userGetsAllTheSharesSharedWithHimUsingTheSharingApi($user); + $this->theResponseShouldNotContainAnyShareIds(); + } + + /** + * @Then /^the response should contain ([0-9]+) entries$/ + * + * @param int $count + * + * @return void + */ + public function checkingTheResponseEntriesCount(int $count):void { + $actualCount = \count($this->getResponseXml(null, __METHOD__)->data[0]); + Assert::assertEquals( + $count, + $actualCount, + "Expected that the response should contain '$count' entries but got '$actualCount' entries" + ); + } + + /** + * @Then the fields of the last response to user :user should include + * + * @param string $user + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function checkFields(string $user, ?TableNode $body):void { + $this->verifyTableNodeColumnsCount($body, 2); + $bodyRows = $body->getRowsHash(); + $userRelatedFieldNames = [ + "owner", + "user", + "uid_owner", + "uid_file_owner", + "share_with", + "displayname_file_owner", + "displayname_owner" + ]; + foreach ($bodyRows as $field => $value) { + if (\in_array($field, $userRelatedFieldNames)) { + $value = $this->substituteInLineCodes($value, $user); + } + $value = $this->getActualUsername($value); + $value = $this->replaceValuesFromTable($field, $value); + Assert::assertTrue( + $this->isFieldInResponse($field, $value), + "$field doesn't have value '$value'" + ); + } + } + + /** + * @Then /^the fields of the last response (?:to|about) user "([^"]*)" sharing with (?:user|group) "([^"]*)" should include$/ + * + * @param string $sharer + * @param string $sharee + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function checkFieldsOfLastResponseToUser(string $sharer, string $sharee, ?TableNode $body):void { + $this->verifyTableNodeColumnsCount($body, 2); + $bodyRows = $body->getRowsHash(); + foreach ($bodyRows as $field => $value) { + if (\in_array($field, ["displayname_owner", "displayname_file_owner", "owner", "uid_owner", "uid_file_owner"])) { + $value = $this->substituteInLineCodes($value, $sharer); + } elseif (\in_array($field, ["share_with", "share_with_displayname", "user"])) { + $value = $this->substituteInLineCodes($value, $sharee); + } + $value = $this->replaceValuesFromTable($field, $value); + Assert::assertTrue( + $this->isFieldInResponse($field, $value), + "$field doesn't have value '$value'" + ); + } + } + + /** + * @Then the last response should be empty + * + * @return void + */ + public function theFieldsOfTheLastResponseShouldBeEmpty():void { + $data = $this->getResponseXml(null, __METHOD__)->data[0]; + Assert::assertEquals( + \count($data->element), + 0, + "last response contains data but was expected to be empty" + ); + } + + /** + * + * @return string + * + * @throws Exception + */ + public function getSharingAttributesFromLastResponse():string { + $responseXml = $this->getResponseXml(null, __METHOD__)->data[0]; + $actualAttributesElement = $responseXml->xpath('//attributes'); + + if ((bool) $actualAttributesElement) { + $actualAttributes = (array) $actualAttributesElement[0]; + if (empty($actualAttributes)) { + throw new Exception( + "No data inside 'attributes' element in the last response." + ); + } + return $actualAttributes[0]; + } + + throw new Exception("No 'attributes' found inside the response of the last share."); + } + + /** + * @Then the additional sharing attributes for the response should include + * + * @param TableNode $attributes + * + * @return void + * @throws Exception + */ + public function checkingAttributesInLastShareResponse(TableNode $attributes):void { + $this->verifyTableNodeColumns($attributes, ['scope', 'key', 'enabled']); + $attributes = $attributes->getHash(); + + // change string "true"/"false" to boolean inside array + \array_walk_recursive( + $attributes, + function (&$value, $key) { + if ($key !== 'enabled') { + return; + } + if ($value === 'true') { + $value = true; + } + if ($value === 'false') { + $value = false; + } + } + ); + + $actualAttributes = $this->getSharingAttributesFromLastResponse(); + + // parse json to array + $actualAttributesArray = \json_decode($actualAttributes, true); + if (\json_last_error() !== JSON_ERROR_NONE) { + $errMsg = \strtolower(\json_last_error_msg()); + throw new Exception( + "JSON decoding failed because of $errMsg in json\n" . + 'Expected data to be json with array of objects. ' . + "\nReceived:\n $actualAttributes" + ); + } + + // check if the expected attributes received from table match actualAttributes + foreach ($attributes as $row) { + $foundRow = false; + foreach ($actualAttributesArray as $item) { + if (($item['scope'] === $row['scope']) + && ($item['key'] === $row['key']) + && ($item['enabled'] === $row['enabled']) + ) { + $foundRow = true; + } + } + Assert::assertTrue( + $foundRow, + "Could not find expected attribute with scope '" . $row['scope'] . "' and key '" . $row['key'] . "'" + ); + } + } + + /** + * @Then the downloading of file :fileName for user :user should fail with error message + * + * @param string $fileName + * @param string $user + * @param PyStringNode $errorMessage + * + * @return void + * @throws Exception + */ + public function userDownloadsFailWithMessage(string $fileName, string $user, PyStringNode $errorMessage):void { + $user = $this->getActualUsername($user); + $this->downloadFileAsUserUsingPassword($user, $fileName); + $receivedErrorMessage = $this->getResponseXml(null, __METHOD__)->xpath('//s:message'); + if ((bool) $errorMessage) { + Assert::assertEquals( + $errorMessage, + (string) $receivedErrorMessage[0], + "Expected error message was '$errorMessage' but got '" + . (string) $receivedErrorMessage[0] + . "'" + ); + return; + } + throw new Exception("No 's:message' element found on the response."); + } + + /** + * @Then the fields of the last response should not include + * + * @param TableNode|null $body + * + * @return void + * @throws Exception + */ + public function checkFieldsNotInResponse(?TableNode $body):void { + $this->verifyTableNodeColumnsCount($body, 2); + $bodyRows = $body->getRowsHash(); + + foreach ($bodyRows as $field => $value) { + $value = $this->replaceValuesFromTable($field, $value); + Assert::assertFalse( + $this->isFieldInResponse($field, $value, false), + "$field has value $value but should not" + ); + } + } + + /** + * @param string $user + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function removeAllSharesFromResource(string $user, string $fileName):void { + $headers = ['Content-Type' => 'application/json']; + $res = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?format=json"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion, + $headers + ); + + $this->setResponse($res); + $this->theHTTPStatusCodeShouldBeSuccess(); + + $json = \json_decode($res->getBody()->getContents(), true); + $deleted = false; + foreach ($json['ocs']['data'] as $data) { + if (\stripslashes($data['path']) === $fileName) { + $id = $data['id']; + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "DELETE", + $this->getSharesEndpointPath("/{$id}"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + + $this->setResponse($response); + $this->theHTTPStatusCodeShouldBeSuccess(); + + $deleted = true; + } + } + + if ($deleted === false) { + throw new Exception( + "Could not delete shares for user $user file $fileName" + ); + } + } + + /** + * @When user :user removes all shares from the file named :fileName using the sharing API + * + * @param string $user + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function userRemovesAllSharesFromTheFileNamed(string $user, string $fileName):void { + $user = $this->getActualUsername($user); + $this->removeAllSharesFromResource($user, $fileName); + } + + /** + * @Given user :user has removed all shares from the file named :fileName + * + * @param string $user + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function userHasRemovedAllSharesFromTheFileNamed(string $user, string $fileName):void { + $user = $this->getActualUsername($user); + $this->removeAllSharesFromResource($user, $fileName); + $dataResponded = $this->getShares($user, $fileName); + Assert::assertEquals( + 0, + \count($dataResponded), + __METHOD__ + . " Expected all shares to be removed from '$fileName' but got '" + . \count($dataResponded) + . "' shares still present" + ); + } + + /** + * Returns shares of a file or folder as a SimpleXMLElement + * + * Note: the "single" SimpleXMLElement may contain one or more actual + * shares (to users, groups or public links etc). If you access an item directly, + * for example, getShares()->id, then the value of "id" for the first element + * will be returned. To access all the elements, you can loop through the + * returned SimpleXMLElement with "foreach" - it will act like a PHP array + * of elements. + * + * @param string $user + * @param string $path + * + * @return SimpleXMLElement + */ + public function getShares(string $user, string $path):SimpleXMLElement { + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $this->getSharesEndpointPath("?path=$path"), + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + return $this->getResponseXml(null, __METHOD__)->data->element; + } + + /** + * @Then /^as user "([^"]*)" the public shares of (?:file|folder) "([^"]*)" should be$/ + * + * @param string $user + * @param string $path + * @param TableNode|null $TableNode + * + * @return void + * @throws Exception + */ + public function checkPublicShares(string $user, string $path, ?TableNode $TableNode):void { + $user = $this->getActualUsername($user); + $dataResponded = $this->getShares($user, $path); + + $this->verifyTableNodeColumns($TableNode, ['path', 'permissions', 'name']); + if ($TableNode instanceof TableNode) { + $elementRows = $TableNode->getHash(); + + foreach ($elementRows as $expectedElementsArray) { + $nameFound = false; + foreach ($dataResponded as $elementResponded) { + if ((string) $elementResponded->name[0] === $expectedElementsArray['name']) { + Assert::assertEquals( + $expectedElementsArray['path'], + (string) $elementResponded->path[0], + __METHOD__ + . " Expected '${expectedElementsArray['path']}' but got '" + . (string) $elementResponded->path[0] + . "'" + ); + Assert::assertEquals( + $expectedElementsArray['permissions'], + (string) $elementResponded->permissions[0], + __METHOD__ + . " Expected '${expectedElementsArray['permissions']}' but got '" + . (string) $elementResponded->permissions[0] + . "'" + ); + $nameFound = true; + break; + } + } + Assert::assertTrue( + $nameFound, + "Shared link name {$expectedElementsArray['name']} not found" + ); + } + } + } + + /** + * @Then /^as user "([^"]*)" the (file|folder) "([^"]*)" should not have any shares$/ + * + * @param string $user + * @param string $entry + * @param string $path + * + * @return void + * @throws Exception + */ + public function checkPublicSharesAreEmpty(string $user, string $entry, string $path):void { + $user = $this->getActualUsername($user); + $this->asFileOrFolderShouldExist($user, $entry, $path); + $dataResponded = $this->getShares($user, $path); + //It shouldn't have public shares + Assert::assertEquals( + 0, + \count($dataResponded), + __METHOD__ + . " As '$user', '$path' was expected to have no shares, but got '" + . \count($dataResponded) + . "' shares present" + ); + } + + /** + * @param string $user + * @param string $path to share + * @param string $name of share + * + * @return string|null + */ + public function getPublicShareIDByName(string $user, string $path, string $name):?string { + $dataResponded = $this->getShares($user, $path); + foreach ($dataResponded as $elementResponded) { + if ((string) $elementResponded->name[0] === $name) { + return (string) $elementResponded->id[0]; + } + } + return null; + } + + /** + * @param string $user + * @param string $name + * @param string $path + * + * @return void + */ + public function deletePublicLinkShareUsingTheSharingApi( + string $user, + string $name, + string $path + ):void { + $user = $this->getActualUsername($user); + $share_id = $this->getPublicShareIDByName($user, $path, $name); + $url = $this->getSharesEndpointPath("/$share_id"); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "DELETE", + $url, + null + ); + } + + /** + * @When /^user "([^"]*)" deletes public link share named "([^"]*)" in (?:file|folder) "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $name + * @param string $path + * + * @return void + */ + public function userDeletesPublicLinkShareNamedUsingTheSharingApi( + string $user, + string $name, + string $path + ):void { + $this->deletePublicLinkShareUsingTheSharingApi( + $user, + $name, + $path + ); + } + + /** + * @Given /^user "([^"]*)" has deleted public link share named "([^"]*)" in (?:file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $name + * @param string $path + * + * @return void + */ + public function userHasDeletedPublicLinkShareNamedUsingTheSharingApi( + string $user, + string $name, + string $path + ):void { + $this->deletePublicLinkShareUsingTheSharingApi( + $user, + $name, + $path + ); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^user "([^"]*)" (declines|accepts) share "([^"]*)" offered by user "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $action + * @param string $share + * @param string $offeredBy + * @param string|null $state specify 'accepted', 'pending', 'rejected' or 'declined' to only consider shares in that state + * + * @return void + * @throws Exception + */ + public function userReactsToShareOfferedBy(string $user, string $action, string $share, string $offeredBy, ?string $state = ''):void { + $user = $this->getActualUsername($user); + $offeredBy = $this->getActualUsername($offeredBy); + + $dataResponded = $this->getAllSharesSharedWithUser($user); + $shareId = null; + foreach ($dataResponded as $shareElement) { + $shareFolder = \trim( + SetupHelper::getSystemConfigValue('share_folder', $this->getStepLineRef()) + ); + + if ($shareFolder) { + $shareFolder = \ltrim($shareFolder, '/'); + } + + // Add share folder to share path if given + if ($shareFolder && !(strpos($share, "/$shareFolder") === 0)) { + $share = '/' . $shareFolder . $share; + } + + // SharingHelper::SHARE_STATES has the mapping between the words for share states + // like "accepted", "pending",... and the integer constants 0, 1,... that are in + // the "state" field of the share data. + if ($state === '') { + // Any share state is OK + $matchesShareState = true; + } else { + $requiredStateCode = SharingHelper::SHARE_STATES[$state]; + if ($shareElement['state'] === $requiredStateCode) { + $matchesShareState = true; + } else { + $matchesShareState = false; + } + } + + if ($matchesShareState + && (string) $shareElement['uid_owner'] === $offeredBy + && (string) $shareElement['path'] === $share + ) { + $shareId = (string) $shareElement['id']; + break; + } + } + Assert::assertNotNull( + $shareId, + __METHOD__ . " could not find share $share, offered by $offeredBy to $user" + ); + $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}" . + "/shares/pending/$shareId"; + if (\substr($action, 0, 7) === "decline") { + $httpRequestMethod = "DELETE"; + } elseif (\substr($action, 0, 6) === "accept") { + $httpRequestMethod = "POST"; + } + + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $httpRequestMethod, + $url, + null + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" (declines|accepts) the following shares offered by user "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $action + * @param string $offeredBy + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userReactsToTheFollowingSharesOfferedBy(string $user, string $action, string $offeredBy, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $share) { + $this->userReactsToShareOfferedBy( + $user, + $action, + $share["path"], + $offeredBy + ); + } + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" (declines|accepts) share with ID "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $action + * @param string $share_id + * + * @return void + * @throws Exception + */ + public function userReactsToShareWithShareIDOfferedBy(string $user, string $action, string $share_id):void { + $user = $this->getActualUsername($user); + + $shareId = $this->substituteInLineCodes($share_id, $user); + + $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}" . + "/shares/pending/$shareId"; + if (\substr($action, 0, 7) === "decline") { + $httpRequestMethod = "DELETE"; + } elseif (\substr($action, 0, 6) === "accept") { + $httpRequestMethod = "POST"; + } + + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + $httpRequestMethod, + $url, + null + ); + } + + /** + * @Given /^user "([^"]*)" has (declined|accepted) share "([^"]*)" offered by user "([^"]*)"$/ + * + * @param string $user + * @param string $action + * @param string $share + * @param string $offeredBy + * + * @return void + * @throws Exception + */ + public function userHasReactedToShareOfferedBy(string $user, string $action, string $share, string $offeredBy):void { + $this->userReactsToShareOfferedBy($user, $action, $share, $offeredBy); + if ($action === 'declined') { + $actionText = 'decline'; + } + if ($action === 'accepted') { + $actionText = 'accept'; + } + $this->theHTTPStatusCodeShouldBe( + 200, + __METHOD__ . " could not $actionText share $share to $user by $offeredBy" + ); + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + } + + /** + * @When /^user "([^"]*)" accepts the (?:first|next|) pending share "([^"]*)" offered by user "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $share + * @param string $offeredBy + * + * @return void + * @throws Exception + */ + public function userAcceptsThePendingShareOfferedBy(string $user, string $share, string $offeredBy):void { + $this->userReactsToShareOfferedBy($user, 'accepts', $share, $offeredBy, 'pending'); + } + + /** + * @Given /^user "([^"]*)" has accepted the (?:first|next|) pending share "([^"]*)" offered by user "([^"]*)"$/ + * + * @param string $user + * @param string $share + * @param string $offeredBy + * + * @return void + * @throws Exception + */ + public function userHasAcceptedThePendingShareOfferedBy(string $user, string $share, string $offeredBy) { + $this->userAcceptsThePendingShareOfferedBy($user, $share, $offeredBy); + $this->theHTTPStatusCodeShouldBe( + 200, + __METHOD__ . " could not accept the pending share $share to $user by $offeredBy" + ); + $this->ocsContext->assertOCSResponseIndicatesSuccess(); + } + + /** + * @Then /^user "([^"]*)" should be able to (decline|accept) pending share "([^"]*)" offered by user "([^"]*)"$/ + * + * @param string $user + * @param string $action + * @param string $share + * @param string $offeredBy + * + * @return void + * @throws Exception + */ + public function userShouldBeAbleToAcceptShareOfferedBy( + string $user, + string $action, + string $share, + string $offeredBy + ) { + if ($action === 'accept') { + $this->userHasAcceptedThePendingShareOfferedBy($user, $share, $offeredBy); + } elseif ($action === 'decline') { + $this->userHasReactedToShareOfferedBy($user, 'declined', $share, $offeredBy); + } + } + + /** + * + * @Then /^the sharing API should report to user "([^"]*)" that these shares are in the (pending|accepted|declined) state$/ + * + * @param string $user + * @param string $state + * @param TableNode $table table with headings that correspond to the attributes + * of the share e.g. "|path|uid_owner|" + * + * @return void + * @throws Exception + */ + public function assertSharesOfUserAreInState(string $user, string $state, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["path"], $this->shareResponseFields); + $usersShares = $this->getAllSharesSharedWithUser($user, $state); + foreach ($table as $row) { + $found = false; + //the API returns the path without trailing slash, but we want to + //be able to accept leading and/or trailing slashes in the step definition + $row['path'] = "/" . \trim($row['path'], "/"); + foreach ($usersShares as $share) { + try { + Assert::assertArrayHasKey('path', $share); + Assert::assertEquals($row['path'], $share['path']); + $found = true; + break; + } catch (PHPUnit\Framework\ExpectationFailedException $e) { + } + } + if (!$found) { + Assert::fail( + "could not find the share with this attributes " . + \print_r($row, true) + ); + } + } + } + + /** + * + * @Then /^the sharing API should report to user "([^"]*)" that no shares are in the (pending|accepted|declined) state$/ + * + * @param string $user + * @param string $state + * + * @return void + * @throws Exception + */ + public function assertNoSharesOfUserAreInState(string $user, string $state):void { + $usersShares = $this->getAllSharesSharedWithUser($user, $state); + Assert::assertEmpty( + $usersShares, + "user has " . \count($usersShares) . " share(s) in the $state state" + ); + } + + /** + * @Then the sharing API should report that no shares are shared with user :user + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function assertThatNoSharesAreSharedWithUser(string $user):void { + $usersShares = $this->getAllSharesSharedWithUser($user); + Assert::assertEmpty( + $usersShares, + "user has " . \count($usersShares) . " share(s)" + ); + } + + /** + * @When the administrator adds group :group to the exclude groups from receiving shares list using the occ command + * + * @param string $group + * + * @return int + * @throws Exception + */ + public function administratorAddsGroupToExcludeFromReceivingSharesList(string $group): int { + //get current groups + $occExitCode = $this->runOcc( + ['config:app:get files_sharing blacklisted_receiver_groups'] + ); + + $occStdOut = $this->getStdOutOfOccCommand(); + $occStdErr = $this->getStdErrOfOccCommand(); + + if (($occExitCode !== 0) && ($occExitCode !== 1)) { + throw new Exception( + "occ config:app:get files_sharing blacklisted_receiver_groups failed with exit code " . + $occExitCode . ", output " . + $occStdOut . ", error output " . + $occStdErr + ); + } + + //if the setting was never set before stdOut will be empty and return code will be 1 + if (\trim($occStdOut) === "") { + $occStdOut = "[]"; + } + + $currentGroups = \json_decode($occStdOut, true); + Assert::assertNotNull( + $currentGroups, + "could not json decode app setting 'blacklisted_receiver_groups' of 'files_sharing'\n" . + "stdOut: '" . $occStdOut . "'\n" . + "stdErr: '" . $occStdErr . "'" + ); + + $currentGroups[] = $group; + return $this->runOcc( + [ + 'config:app:set', + 'files_sharing blacklisted_receiver_groups', + '--value=' . \json_encode($currentGroups) + ] + ); + } + + /** + * @Given the administrator has added group :group to the exclude groups from receiving shares list + * + * @param string $group + * + * @return void + * @throws Exception + */ + public function administratorHasAddedGroupToExcludeFromReceivingSharesList(string $group):void { + $setSettingExitCode = $this->administratorAddsGroupToExcludeFromReceivingSharesList($group); + if ($setSettingExitCode !== 0) { + throw new Exception( + __METHOD__ . " could not set files_sharing blacklisted_receiver_groups " . + $setSettingExitCode . " " . + $this->getStdOutOfOccCommand() . " " . + $this->getStdErrOfOccCommand() + ); + } + } + + /** + * @When user :user gets share with id :share using the sharing API + * + * @param string $user + * @param string $share_id + * + * @return ResponseInterface|null + */ + public function userGetsTheLastShareWithTheShareIdUsingTheSharingApi(string $user, string $share_id): ?ResponseInterface { + $user = $this->getActualUsername($user); + $share_id = $this->substituteInLineCodes($share_id, $user); + $url = $this->getSharesEndpointPath("/$share_id"); + + $this->response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "GET", + $url, + $this->getStepLineRef(), + [], + $this->ocsApiVersion + ); + return $this->response; + } + + /** + * + * @param string $user + * @param string|null $state pending|accepted|declined|rejected|all + * + * @return array of shares that are shared with this user + * @throws Exception + */ + private function getAllSharesSharedWithUser(string $user, ?string $state = "all"): array { + switch ($state) { + case 'pending': + case 'accepted': + case 'declined': + case 'rejected': + $stateCode = SharingHelper::SHARE_STATES[$state]; + break; + case 'all': + $stateCode = "all"; + break; + default: + throw new InvalidArgumentException( + __METHOD__ . ' invalid "state" given' + ); + break; + } + + $url = $this->getSharesEndpointPath("?format=json&shared_with_me=true&state=$stateCode"); + $this->ocsContext->userSendsHTTPMethodToOcsApiEndpointWithBody( + $user, + "GET", + $url, + null + ); + if ($this->response->getStatusCode() !== 200) { + throw new Exception( + __METHOD__ . " could not retrieve information about shares" + ); + } + $result = $this->response->getBody()->getContents(); + $usersShares = \json_decode($result, true); + if (!\is_array($usersShares)) { + throw new Exception( + __METHOD__ . " API result about shares is not valid JSON" + ); + } + return $usersShares['ocs']['data']; + } + + /** + * The tests can create public link shares with the API or with the webUI. + * If lastPublicShareData is null, then there have not been any created with the API, + * so look for details of a public link share created with the webUI. + * + * @return string authorization token + */ + public function getLastPublicShareToken():string { + if ($this->lastPublicShareData === null) { + return $this->getLastCreatedPublicLinkToken(); + } else { + if (\count($this->lastPublicShareData->data->element) > 0) { + return (string)$this->lastPublicShareData->data[0]->token; + } + + return (string)$this->lastPublicShareData->data->token; + } + } + + /** + * Returns the attribute values from the last public link share data + * + * @param $attr - attribute name to get + * + * @return string + * @throws Exception + */ + public function getLastPublicShareAttribute(string $attr): string { + if ($this->lastPublicShareData === null) { + throw new Exception(__METHOD__ . "No public share data available."); + } + if (!\in_array($attr, $this->shareResponseFields)) { + throw new Exception( + __METHOD__ . " attribute $attr is not in the list of allowed attributes" + ); + } + if (\count($this->lastPublicShareData->data->element) > 0) { + if (!isset($this->lastPublicShareData->data[0]->$attr)) { + throw new Exception(__METHOD__ . " No attribute $attr available in the last share data."); + } + return (string)$this->lastPublicShareData->data[0]->{$attr}; + } + + if (!isset($this->lastPublicShareData->data->$attr)) { + throw new Exception(__METHOD__ . " No attribute $attr available in the last share data."); + } + + return (string)$this->lastPublicShareData->data->{$attr}; + } + + /** + * @return string path of file that was shared (relevant when a single file has been shared) + */ + public function getLastPublicSharePath():string { + if ($this->lastPublicShareData === null) { + // There have not been any public links created with the API + // so get the path of the last public link created with the webUI + return $this->getLastCreatedPublicLinkPath(); + } else { + if (\count($this->lastPublicShareData->data->element) > 0) { + return (string)$this->lastPublicShareData->data[0]->path; + } + + return (string)$this->lastPublicShareData->data->path; + } + } + + /** + * Send request for preview of a file in a public link + * + * @param string $fileName + * @param string $token + * + * @return void + */ + public function getPublicPreviewOfFile(string $fileName, string $token):void { + $url = $this->getBaseUrl() . + "/index.php/apps/files_sharing/ajax/publicpreview.php" . + "?file=$fileName&t=$token"; + $resp = HttpRequestHelper::get( + $url, + $this->getStepLineRef() + ); + $this->setResponse($resp); + } + + /** + * @When the public accesses the preview of file :path from the last shared public link using the sharing API + * + * @param string $path + * + * @return void + */ + public function thePublicAccessesThePreviewOfTheSharedFileUsingTheSharingApi(string $path):void { + $shareData = $this->getLastPublicShareData(); + $token = (string) $shareData->data->token; + $this->getPublicPreviewOfFile($path, $token); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When the public accesses the preview of the following files from the last shared public link using the sharing API + * + * @param TableNode $table + * + * @throws Exception + * @return void + */ + public function thePublicAccessesThePreviewOfTheFollowingSharedFileUsingTheSharingApi( + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + $this->emptyLastHTTPStatusCodesArray(); + $this->emptyLastOCSStatusCodesArray(); + foreach ($paths as $path) { + $shareData = $this->getLastPublicShareData(); + $token = (string) $shareData->data->token; + $this->getPublicPreviewOfFile($path["path"], $token); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @param string $user + * @param string $shareServer + * @param string|null $password + * + * @return void + */ + public function saveLastSharedPublicLinkShare( + string $user, + string $shareServer, + ?string $password = "" + ):void { + $user = $this->getActualUsername($user); + $userPassword = $this->getPasswordForUser($user); + + $shareData = $this->getLastPublicShareData(); + $owner = (string) $shareData->data->uid_owner; + $name = $this->encodePath((string) $shareData->data->file_target); + $name = \trim($name, "/"); + $ownerDisplayName = (string) $shareData->data->displayname_owner; + $token = (string) $shareData->data->token; + + if (\strtolower($shareServer) == "remote") { + $remote = $this->getRemoteBaseUrl(); + } else { + $remote = $this->getLocalBaseUrl(); + } + + $body['remote'] = $remote; + $body['token'] = $token; + $body['owner'] = $owner; + $body['ownerDisplayName'] = $ownerDisplayName; + $body['name'] = $name; + $body['password'] = $password; + + Assert::assertNotNull( + $token, + __METHOD__ . " could not find any public share" + ); + + $url = $this->getBaseUrl() . "/index.php/apps/files_sharing/external"; + + $response = HttpRequestHelper::post( + $url, + $this->getStepLineRef(), + $user, + $userPassword, + null, + $body + ); + $this->setResponse($response); + } + + /** + * @Given /^user "([^"]*)" has added the public share created from server "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $shareServer + * + * @return void + */ + public function userHasAddedPublicShareCreatedByUser(string $user, string $shareServer):void { + $this->saveLastSharedPublicLinkShare($user, $shareServer); + + $resBody = json_decode($this->response->getBody()->getContents()); + $status = ''; + $message = ''; + if ($resBody) { + $status = $resBody->status; + $message = $resBody->data->message; + } + + Assert::assertEquals( + 200, + $this->response->getStatusCode(), + __METHOD__ + . " Expected status code is '200' but got '" + . $this->response->getStatusCode() + . "'" + ); + Assert::assertNotEquals( + 'error', + $status, + __METHOD__ + . "\nFailed to save public share.\n'$message'" + ); + } + + /** + * @param string $user + * @param string $path + * @param string $type + * @param int $permissions + * + * @return array + */ + public function preparePublicQuickLinkPayload(string $user, string $path, string $type, int $permissions = 1): array { + return [ + "permissions" => $permissions, + "expireDate" => "", + "shareType" => 3, + "itemType" => $type, + "itemSource" => $this->getFileIdForPath($user, $path), + "name" => "Public quick link", + "attributes" => [ + [ + "scope" => "files_sharing", + "key" => "isQuickLink", + "value" => true + ] + ], + "path" => $path + ]; + } + + /** + * @Given /^user "([^"]*)" has created a read only public link for (file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $type + * @param string $path + * + * @return void + * @throws Exception + */ + public function theUserHasCreatedAReadOnlyPublicLinkForFileFolder(string $user, string $type, string $path):void { + $user = $this->getActualUsername($user); + $userPassword = $this->getPasswordForUser($user); + + $requestPayload = $this->preparePublicQuickLinkPayload($user, $path, $type); + $url = $this->getBaseUrl() . "/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json"; + + $response = HttpRequestHelper::post( + $url, + $this->getStepLineRef(), + $user, + $userPassword, + null, + $requestPayload + ); + $this->setResponse($response); + $this->theHTTPStatusCodeShouldBe(200); + } + + /** + * @When /^user "([^"]*)" adds the public share created from server "([^"]*)" using the sharing API$/ + * + * @param string $user + * @param string $shareServer + * + * @return void + */ + public function userAddsPublicShareCreatedByUser(string $user, string $shareServer):void { + $this->saveLastSharedPublicLinkShare($user, $shareServer); + } + + /** + * Expires last created public link share using the testing API + * + * @return void + * @throws GuzzleException + */ + public function expireLastCreatedPublicLinkShare():void { + $shareId = $this->getLastPublicLinkShareId(); + $this->expireShare($shareId); + } + + /** + * Expires a share using the testing API + * + * @param string|null $shareId optional share id, if null then expire the last share that was created. + * + * @return void + * @throws GuzzleException + */ + public function expireShare(string $shareId = null):void { + $adminUser = $this->getAdminUsername(); + if ($shareId === null) { + $shareId = $this->getLastShareId(); + } + $response = OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $adminUser, + $this->getAdminPassword(), + 'POST', + "/apps/testing/api/v1/expire-share/{$shareId}", + $this->getStepLineRef(), + [], + $this->getOcsApiVersion() + ); + $this->setResponse($response); + } + + /** + * @Given the administrator has expired the last created share using the testing API + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorHasExpiredTheLastCreatedShare():void { + $this->expireShare(); + $httpStatus = $this->getResponse()->getStatusCode(); + Assert::assertSame( + 200, + $httpStatus, + "Request to expire last share failed. HTTP status was '$httpStatus'" + ); + $ocsStatusMessage = $this->ocsContext->getOCSResponseStatusMessage($this->getResponse()); + if ($this->getOcsApiVersion() === 1) { + $expectedOcsStatusCode = "100"; + } else { + $expectedOcsStatusCode = "200"; + } + $this->ocsContext->theOCSStatusCodeShouldBe( + $expectedOcsStatusCode, + "Request to expire last share failed: '$ocsStatusMessage'" + ); + } + + /** + * @Given the administrator has expired the last created public link share using the testing API + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorHasExpiredTheLastCreatedPublicLinkShare():void { + $this->expireLastCreatedPublicLinkShare(); + $httpStatus = $this->getResponse()->getStatusCode(); + Assert::assertSame( + 200, + $this->getResponse()->getStatusCode(), + "Request to expire last public link share failed. HTTP status was '$httpStatus'" + ); + $ocsStatusMessage = $this->ocsContext->getOCSResponseStatusMessage($this->getResponse()); + if ($this->getOcsApiVersion() === 1) { + $expectedOcsStatusCode = "100"; + } else { + $expectedOcsStatusCode = "200"; + } + $this->ocsContext->theOCSStatusCodeShouldBe( + $expectedOcsStatusCode, + "Request to expire last public link share failed: '$ocsStatusMessage'" + ); + } + + /** + * @When the administrator expires the last created share using the testing API + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorExpiresTheLastCreatedShare():void { + $this->expireShare(); + } + + /** + * @When the administrator expires the last created public link share using the testing API + * + * @return void + * @throws GuzzleException + */ + public function theAdministratorExpiresTheLastCreatedPublicLinkShare():void { + $this->expireLastCreatedPublicLinkShare(); + } + + /** + * replace values from table + * + * @param string $field + * @param string $value + * + * @return string + */ + public function replaceValuesFromTable(string $field, string $value):string { + if (\substr($field, 0, 10) === "share_with") { + $value = \str_replace( + "REMOTE", + $this->getRemoteBaseUrl(), + $value + ); + $value = \str_replace( + "LOCAL", + $this->getLocalBaseUrl(), + $value + ); + } + if (\substr($field, 0, 6) === "remote") { + $value = \str_replace( + "REMOTE", + $this->getRemoteBaseUrl(), + $value + ); + $value = \str_replace( + "LOCAL", + $this->getLocalBaseUrl(), + $value + ); + } + if ($field === "permissions") { + if (\is_string($value) && !\is_numeric($value)) { + $value = $this->splitPermissionsString($value); + } + $value = (string)SharingHelper::getPermissionSum($value); + } + if ($field === "share_type") { + $value = (string)SharingHelper::getShareType($value); + } + return $value; + } + + /** + * @return array of common sharing capability settings for testing + */ + protected function getCommonSharingConfigs():array { + return [ + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'auto_accept_share', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_auto_accept_share', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'api_enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_enabled', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_links', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@upload', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_public_upload', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'group_sharing', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_group_sharing', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'share_with_group_members_only', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_only_share_with_group_members', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'share_with_membership_groups_only', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_only_share_with_membership_groups', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'exclude_groups_from_sharing', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_exclude_groups', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'exclude_groups_from_sharing_list', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_exclude_groups_list', + 'testingState' => '' + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => + 'user_enumeration@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => + 'shareapi_allow_share_dialog_user_enumeration', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => + 'user_enumeration@@@group_members_only', + 'testingApp' => 'core', + 'testingParameter' => + 'shareapi_share_dialog_user_enumeration_group_members', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'resharing', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_resharing', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => + 'public@@@password@@@enforced_for@@@read_only', + 'testingApp' => 'core', + 'testingParameter' => + 'shareapi_enforce_links_password_read_only', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => + 'public@@@password@@@enforced_for@@@read_write', + 'testingApp' => 'core', + 'testingParameter' => + 'shareapi_enforce_links_password_read_write', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => + 'public@@@password@@@enforced_for@@@upload_only', + 'testingApp' => 'core', + 'testingParameter' => + 'shareapi_enforce_links_password_write_only', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@send_mail', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_public_notification', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@social_share', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_social_share', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@expire_date@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_default_expire_date', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'public@@@expire_date@@@enforced', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_enforce_expire_date', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'user@@@send_mail', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_allow_mail_notification', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'user@@@expire_date@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_default_expire_date_user_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'user@@@expire_date@@@enforced', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_enforce_expire_date_user_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'group@@@expire_date@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_default_expire_date_group_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'group@@@expire_date@@@enforced', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_enforce_expire_date_group_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'remote@@@expire_date@@@enabled', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_default_expire_date_remote_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'remote@@@expire_date@@@enforced', + 'testingApp' => 'core', + 'testingParameter' => 'shareapi_enforce_expire_date_remote_share', + 'testingState' => false + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'federation@@@outgoing', + 'testingApp' => 'files_sharing', + 'testingParameter' => 'outgoing_server2server_share_enabled', + 'testingState' => true + ], + [ + 'capabilitiesApp' => 'files_sharing', + 'capabilitiesParameter' => 'federation@@@incoming', + 'testingApp' => 'files_sharing', + 'testingParameter' => 'incoming_server2server_share_enabled', + 'testingState' => true + ] + ]; + } +} diff --git a/tests/acceptance/features/bootstrap/TUSContext.php b/tests/acceptance/features/bootstrap/TUSContext.php new file mode 100644 index 00000000000..15df1b8a366 --- /dev/null +++ b/tests/acceptance/features/bootstrap/TUSContext.php @@ -0,0 +1,489 @@ + + * + * @copyright Copyright (c) 2020, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Exception\GuzzleException; +use TestHelpers\HttpRequestHelper; +use TestHelpers\WebDavHelper; +use TusPhp\Exception\ConnectionException; +use TusPhp\Exception\TusException; +use TusPhp\Tus\Client; +use PHPUnit\Framework\Assert; + +require_once 'bootstrap.php'; + +/** + * TUS related test steps + */ +class TUSContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + private $resourceLocation = null; + + /** + * @When user :user creates a new TUS resource on the WebDAV API with these headers: + * + * @param string $user + * @param TableNode $headers + * @param string $content + * + * @return void + * + * @throws Exception + * @throws GuzzleException + */ + public function createNewTUSResourceWithHeaders(string $user, TableNode $headers, string $content = ''): void { + $this->featureContext->verifyTableNodeColumnsCount($headers, 2); + $user = $this->featureContext->getActualUsername($user); + $password = $this->featureContext->getUserPassword($user); + $this->resourceLocation = null; + + $this->featureContext->setResponse( + $this->featureContext->makeDavRequest( + $user, + "POST", + null, + $headers->getRowsHash(), + $content, + "files", + null, + false, + $password + ) + ); + $locationHeader = $this->featureContext->getResponse()->getHeader('Location'); + if (\sizeof($locationHeader) > 0) { + $this->resourceLocation = $locationHeader[0]; + } + } + + /** + * @Given user :user has created a new TUS resource on the WebDAV API with these headers: + * + * @param string $user + * @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically + * + * @return void + * + * @throws Exception + * @throws GuzzleException + */ + public function createNewTUSResource(string $user, TableNode $headers): void { + $rows = $headers->getRows(); + $rows[] = ['Tus-Resumable', '1.0.0']; + $this->createNewTUSResourceWithHeaders($user, new TableNode($rows)); + $this->featureContext->theHTTPStatusCodeShouldBe(201); + } + + /** + * @When /^user "([^"]*)" sends a chunk to the last created TUS Location with offset "([^"]*)" and data "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $offset + * @param string $data + * @param string $checksum + * + * @return void + * + * @throws GuzzleException + * @throws JsonException + */ + public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data, string $checksum = ''): void { + $user = $this->featureContext->getActualUsername($user); + $password = $this->featureContext->getUserPassword($user); + $this->featureContext->setResponse( + HttpRequestHelper::sendRequest( + $this->resourceLocation, + $this->featureContext->getStepLineRef(), + 'PATCH', + $user, + $password, + [ + 'Content-Type' => 'application/offset+octet-stream', + 'Tus-Resumable' => '1.0.0', + 'Upload-Checksum' => $checksum, + 'Upload-Offset' => $offset + ], + $data + ) + ); + WebDavHelper::$SPACE_ID_FROM_OCIS = ''; + } + + /** + * @When user :user uploads file :source to :destination using the TUS protocol on the WebDAV API + * + * @param string|null $user + * @param string $source + * @param string $destination + * @param array $uploadMetadata array of metadata to be placed in the + * `Upload-Metadata` header. + * see https://tus.io/protocols/resumable-upload.html#upload-metadata + * Don't Base64 encode the value. + * @param int $noOfChunks + * @param int|null $bytes + * @param string $checksum + * + * @return void + * @throws ConnectionException + * @throws GuzzleException + * @throws JsonException + * @throws ReflectionException + * @throws TusException + */ + public function userUploadsUsingTusAFileTo( + ?string $user, + string $source, + string $destination, + array $uploadMetadata = [], + int $noOfChunks = 1, + int $bytes = null, + string $checksum = '' + ): void { + $user = $this->featureContext->getActualUsername($user); + $password = $this->featureContext->getUserPassword($user); + $headers = [ + 'Authorization' => 'Basic ' . \base64_encode($user . ':' . $password) + ]; + if ($bytes !== null) { + $creationWithUploadHeader = [ + 'Content-Type' => 'application/offset+octet-stream', + 'Tus-Resumable' => '1.0.0' + ]; + $headers = \array_merge($headers, $creationWithUploadHeader); + } + if ($checksum != '') { + $checksumHeader = [ + 'Upload-Checksum' => $checksum + ]; + $headers = \array_merge($headers, $checksumHeader); + } + + $client = new Client( + $this->featureContext->getBaseUrl(), + ['verify' => false, + 'headers' => $headers + ] + ); + $client->setApiPath( + WebDavHelper::getDavPath( + $user, + $this->featureContext->getDavPathVersion(), + "files", + WebDavHelper::$SPACE_ID_FROM_OCIS + ? WebDavHelper::$SPACE_ID_FROM_OCIS + : $this->featureContext->getPersonalSpaceIdForUser($user) + ) + ); + WebDavHelper::$SPACE_ID_FROM_OCIS = ''; + $client->setMetadata($uploadMetadata); + $sourceFile = $this->featureContext->acceptanceTestsDirLocation() . $source; + $client->setKey((string)rand())->file($sourceFile, $destination); + $this->featureContext->pauseUploadDelete(); + + if ($bytes !== null) { + $client->file($sourceFile, $destination)->createWithUpload($client->getKey(), $bytes); + } elseif ($noOfChunks === 1) { + $client->file($sourceFile, $destination)->upload(); + } else { + $bytesPerChunk = (int)\ceil(\filesize($sourceFile) / $noOfChunks); + for ($i = 0; $i < $noOfChunks; $i++) { + $client->upload($bytesPerChunk); + } + } + $this->featureContext->setLastUploadDeleteTime(\time()); + } + + /** + * @When user :user uploads file with content :content to :destination using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $content + * @param string $destination + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public function userUploadsAFileWithContentToUsingTus( + string $user, + string $content, + string $destination + ): void { + $tmpfname = $this->writeDataToTempFile($content); + try { + $this->userUploadsUsingTusAFileTo( + $user, + \basename($tmpfname), + $destination + ); + } catch (Exception $e) { + Assert::assertStringContainsString('TusPhp\Exception\FileException: Unable to create resource', (string)$e); + } + \unlink($tmpfname); + } + + /** + * @When user :user uploads file with content :content in :noOfChunks chunks to :destination using the TUS protocol on the WebDAV API + * + * @param string|null $user + * @param string $content + * @param int|null $noOfChunks + * @param string $destination + * + * @return void + * @throws ConnectionException + * @throws GuzzleException + * @throws JsonException + * @throws ReflectionException + * @throws TusException + * @throws Exception + * @throws GuzzleException + */ + public function userUploadsAFileWithContentInChunksUsingTus( + ?string $user, + string $content, + ?int $noOfChunks, + string $destination + ): void { + $tmpfname = $this->writeDataToTempFile($content); + $this->userUploadsUsingTusAFileTo( + $user, + \basename($tmpfname), + $destination, + [], + $noOfChunks + ); + \unlink($tmpfname); + } + + /** + * @When user :user uploads file :source to :destination with mtime :mtime using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * @param string $mtime Time in human readable format is taken as input which is converted into milliseconds that is used by API + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function userUploadsFileWithContentToWithMtimeUsingTUS( + string $user, + string $source, + string $destination, + string $mtime + ): void { + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + $user = $this->featureContext->getActualUsername($user); + $this->userUploadsUsingTusAFileTo( + $user, + $source, + $destination, + ['mtime' => $mtime] + ); + } + + /** + * @param string $content + * + * @return string the file name + * @throws Exception + */ + private function writeDataToTempFile(string $content): string { + $tmpfname = \tempnam( + $this->featureContext->acceptanceTestsDirLocation(), + "tus-upload-test-" + ); + if ($tmpfname === false) { + throw new \Exception("could not create a temporary filename"); + } + $tempfile = \fopen($tmpfname, "w"); + if ($tempfile === false) { + throw new \Exception("could not open " . $tmpfname . " for write"); + } + \fwrite($tempfile, $content); + \fclose($tempfile); + return $tmpfname; + } + + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function setUpScenario(BeforeScenarioScope $scope): void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } + + /** + * @When user :user creates a new TUS resource with content :content on the WebDAV API with these headers: + * + * @param string $user + * @param string $content + * @param TableNode $headers + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function userCreatesWithUpload( + string $user, + string $content, + TableNode $headers + ): void { + $this->createNewTUSResourceWithHeaders($user, $headers, $content); + } + + /** + * @When user :user creates file :source and uploads content :content in the same request using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $source + * @param string $content + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function userUploadsWithCreatesWithUpload( + string $user, + string $source, + string $content + ): void { + $tmpfname = $this->writeDataToTempFile($content); + $this->userUploadsUsingTusAFileTo( + $user, + \basename($tmpfname), + $source, + [], + 1, + -1 + ); + \unlink($tmpfname); + } + + /** + * @When user :user uploads file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $checksum + * @param string $offset + * @param string $content + * + * @return void + * @throws Exception + */ + public function userUploadsFileWithChecksum( + string $user, + string $checksum, + string $offset, + string $content + ): void { + $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + } + + /** + * @Given user :user has uploaded file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $checksum + * @param string $offset + * @param string $content + * + * @return void + * @throws Exception + */ + public function userHasUploadedFileWithChecksum( + string $user, + string $checksum, + string $offset, + string $content + ): void { + $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $this->featureContext->theHTTPStatusCodeShouldBe(204, ""); + } + + /** + * @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $offset + * @param string $data + * @param string $checksum + * + * @return void + * @throws Exception + */ + public function userUploadsChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void { + $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + } + + /** + * @Given user :user has uploaded a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API + * + * @param string $user + * @param string $offset + * @param string $data + * @param string $checksum + * + * @return void + * @throws Exception + */ + public function userHasUploadedChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void { + $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $this->featureContext->theHTTPStatusCodeShouldBe(204, ""); + } + + /** + * @When user :user overwrites recently shared file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers: + * @When user :user overwrites existing file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers: + * + * @param string $user + * @param string $offset + * @param string $data + * @param string $checksum + * @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically + * + * @return void + * + * @throws GuzzleException + * @throws Exception + */ + public function userOverwritesFileWithChecksum(string $user, string $offset, string $data, string $checksum, TableNode $headers): void { + $this->createNewTUSResource($user, $headers); + $this->userHasUploadedChunkFileWithChecksum($user, $offset, $data, $checksum); + } +} diff --git a/tests/acceptance/features/bootstrap/TagsContext.php b/tests/acceptance/features/bootstrap/TagsContext.php new file mode 100644 index 00000000000..f86b9baeec1 --- /dev/null +++ b/tests/acceptance/features/bootstrap/TagsContext.php @@ -0,0 +1,2010 @@ + + * @author Sergio Bertolin + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use Psr\Http\Message\ResponseInterface; +use PHPUnit\Framework\Assert; +use TestHelpers\TagsHelper; +use TestHelpers\WebDavHelper; +use TestHelpers\HttpRequestHelper; + +require_once 'bootstrap.php'; + +/** + * Acceptance test steps related to testing tags features + */ +class TagsContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @var array + */ + private $createdTags = []; + + /** + * @param string $user + * @param string $userVisible "true", "1" or "false", "0" + * @param string $userAssignable "true", "1" or "false", "0" + * @param string $userEditable "true", "1" or "false", "0" + * @param string $name + * @param string|null $groups + * + * @return void + */ + private function createTag( + string $user, + string $userVisible, + string $userAssignable, + string $userEditable, + string $name, + ?string $groups = null + ):void { + $response = TagsHelper::createTag( + $this->featureContext->getBaseUrl(), + $this->featureContext->getActualUsername($user), + $this->featureContext->getPasswordForUser($user), + $name, + $this->featureContext->getStepLineRef(), + $userVisible, + $userAssignable, + $userEditable, + $groups, + $this->featureContext->getDavPathVersion('systemtags') + ); + $this->featureContext->setResponse($response); + $responseHeaders = $response->getHeaders(); + if (isset($responseHeaders['Content-Location'][0])) { + $tagUrl = $responseHeaders['Content-Location'][0]; + $lastTagId = \substr($tagUrl, \strrpos($tagUrl, '/') + 1); + $this->createdTags[$lastTagId]['name'] = $name; + $this->createdTags[$lastTagId]['userAssignable'] + = (($userAssignable === 'true') || ($userAssignable === '1')); + } + } + + /** + * Adds to the list of created tags using display name + * + * @param string $tagDisplayName + * + * @return void + */ + public function addToTheListOfCreatedTagsByDisplayName(string $tagDisplayName):void { + $tagId = $this->findTagIdByName($tagDisplayName); + $this->createdTags[$tagId]['name'] = $tagDisplayName; + $this->createdTags[$tagId]['userAssignable'] = true; + } + + /** + * Return list of created tags + * + * @return array + */ + public function getListOfCreatedTags():array { + return $this->createdTags; + } + + /** + * @param SimpleXMLElement $tagData + * @param string $type + * + * @return void + * @throws Exception + */ + private function assertTypeOfTag(SimpleXMLElement $tagData, string $type):void { + $userAttributes = TagsHelper::validateTypeOfTag($type); + $userVisible = $userAttributes[0]; + $userAssignable = $userAttributes[1]; + + $tagDisplayName = $tagData->xpath(".//oc:display-name"); + Assert::assertArrayHasKey( + 0, + $tagDisplayName, + "cannot find 'oc:display-name' property" + ); + $tagDisplayName = $tagDisplayName[0]->__toString(); + + $tagUserVisible = $tagData->xpath(".//oc:user-visible"); + Assert::assertArrayHasKey( + 0, + $tagUserVisible, + "cannot find 'oc:user-visible' property" + ); + $tagUserVisible = $tagUserVisible[0]->__toString(); + + $tagUserAssignable = $tagData->xpath(".//oc:user-assignable"); + Assert::assertArrayHasKey( + 0, + $tagUserAssignable, + "cannot find 'oc:user-assignable' property" + ); + $tagUserAssignable = $tagUserAssignable[0]->__toString(); + if (($tagUserVisible !== $userVisible) + || ($tagUserAssignable !== $userAssignable) + ) { + Assert::fail( + "tag $tagDisplayName is not of type $type" + ); + } + } + + /** + * @param string $type + * @param string $name + * @param boolean $useTrueFalseStrings use the strings "true"/"false" else "1"/"0" + * + * @return void + * @throws Exception + */ + public function createTagWithNameAsAdmin(string $type, string $name, bool $useTrueFalseStrings = true):void { + $this->createTagWithName( + $this->featureContext->getAdminUsername(), + $type, + $name, + $useTrueFalseStrings + ); + } + + /** + * @When the administrator creates a :type tag with name :name using the WebDAV API + * + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesATagWithName(string $type, string $name):void { + $this->createTagWithNameAsAdmin( + $type, + $name + ); + } + + /** + * @When the administrator creates the following tags using the WebDAV API + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesFollowingTags(TableNode $table):void { + $this->featureContext->emptyLastHTTPStatusCodesArray(); + $this->featureContext->verifyTableNodeColumns($table, ['name', 'type']); + foreach ($table->getHash() as $row) { + $this->createTagWithNameAsAdmin( + $row['type'], + $row['name'] + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @When /^the administrator creates a "([^"]*)" tag with name "([^"]*)" sending (true-false-strings|numbers) in the request using the WebDAV API$/ + * + * @param string $type + * @param string $name + * @param string $stringsOrNumbers + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesATagWithNameSending(string $type, string $name, string $stringsOrNumbers):void { + if ($stringsOrNumbers === "true-false-strings") { + $useTrueFalseStrings = true; + } else { + $useTrueFalseStrings = true; + } + + $this->createTagWithNameAsAdmin( + $type, + $name, + $useTrueFalseStrings + ); + } + + /** + * @Given the administrator has created a :type tag with name :name + * + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCreatedATagWithName(string $type, string $name):void { + $this->createTagWithNameAsAdmin( + $type, + $name + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $type + * @param string $name + * @param boolean $useTrueFalseStrings use the strings "true"/"false" else "1"/"0" + * + * @return void + * @throws Exception + */ + public function createTagWithNameAsCurrentUser(string $type, string $name, bool $useTrueFalseStrings = true):void { + $this->createTagWithName( + $this->featureContext->getCurrentUser(), + $type, + $name, + $useTrueFalseStrings + ); + } + + /** + * @When the user creates a :type tag with name :name using the WebDAV API + * + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function theUserCreatesATagWithName(string $type, string $name):void { + $this->createTagWithNameAsCurrentUser( + $type, + $name + ); + } + + /** + * @Given the user has created a :type tag with name :name + * + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function theUserHasCreatedATagWithName(string $type, string $name):void { + $this->createTagWithNameAsCurrentUser( + $type, + $name + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $type + * @param string $name + * @param boolean $useTrueFalseStrings use the strings "true"/"false" else "1"/"0" + * + * @return void + * @throws Exception + */ + public function createTagWithName(string $user, string $type, string $name, bool $useTrueFalseStrings = true) { + $user = $this->featureContext->getActualUsername($user); + $this->createTag( + $user, + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[0], + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[1], + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[2], + $name + ); + } + + /** + * @When user :user creates a :type tag with name :name using the WebDAV API + * + * @param string $user + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function userCreatesATagWithName(string $user, string $type, string $name):void { + $this->createTagWithName( + $user, + $type, + $name + ); + } + + /** + * @When /^user "([^"]*)" creates a "([^"]*)" tag with name "([^"]*)" sending (true-false-strings|numbers) in the request using the WebDAV API$/ + * + * @param string $user + * @param string $type + * @param string $name + * @param string $stringsOrNumbers + * + * @return void + * @throws Exception + */ + public function userCreatesATagWithNameSendingInTheRequest( + string $user, + string $type, + string $name, + string $stringsOrNumbers + ):void { + if ($stringsOrNumbers === "true-false-strings") { + $useTrueFalseStrings = true; + } else { + $useTrueFalseStrings = false; + } + + $this->createTagWithName( + $user, + $type, + $name, + $useTrueFalseStrings + ); + } + + /** + * @Given user :user has created a :type tag with name :name + * + * @param string $user + * @param string $type + * @param string $name + * + * @return void + * @throws Exception + */ + public function userHasCreatedATagWithName(string $user, string $type, string $name):void { + $this->createTagWithName( + $user, + $type, + $name + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function createTagWithNameAndGroupsAsCurrentUser(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroups( + $this->featureContext->getCurrentUser(), + $type, + $name, + $groups + ); + } + + /** + * @When the user creates a :type tag with name :name and groups :groups using the WebDAV API + * + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theUserCreatesATagWithNameAndGroups(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroupsAsCurrentUser( + $type, + $name, + $groups + ); + } + + /** + * @Given the user has created a :type tag with name :name and groups :groups + * + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theUserHasCreatedATagWithNameAndGroups(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroupsAsCurrentUser( + $type, + $name, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function createTagWithNameAndGroupsAsAdmin(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroups( + $this->featureContext->getAdminUsername(), + $type, + $name, + $groups + ); + } + + /** + * @When the administrator creates a :type tag with name :name and groups :groups using the WebDAV API + * + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theAdministratorCreatesATagWithNameAndGroups(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroupsAsAdmin( + $type, + $name, + $groups + ); + } + + /** + * @Given the administrator has created a :type tag with name :name and groups :groups + * + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theAdministratorHasCreatedATagWithNameAndGroups(string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroupsAsAdmin( + $type, + $name, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $type + * @param string $name + * @param string $groups + * @param boolean $useTrueFalseStrings use the strings "true"/"false" else "1"/"0" + * + * @return void + * @throws Exception + */ + public function createTagWithNameAndGroups(string $user, string $type, string $name, string $groups, bool $useTrueFalseStrings = true):void { + $this->createTag( + $user, + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[0], + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[1], + TagsHelper::validateTypeOfTag($type, $useTrueFalseStrings)[2], + $name, + $groups + ); + } + + /** + * @When user :user creates a :type tag with name :name and groups :groups using the WebDAV API + * + * @param string $user + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function userCreatesATagWithNameAndGroups(string $user, string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroups( + $user, + $type, + $name, + $groups + ); + } + + /** + * @Given user :user has created a :type tag with name :name and groups :groups + * + * @param string $user + * @param string $type + * @param string $name + * @param string $groups + * + * @return void + * @throws Exception + */ + public function userHasCreatedATagWithNameAndGroups(string $user, string $type, string $name, string $groups):void { + $this->createTagWithNameAndGroups( + $user, + $type, + $name, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param bool $withGroups + * + * @return SimpleXMLElement + */ + public function requestTagsForUser(string $user, bool $withGroups = false):SimpleXMLElement { + return TagsHelper:: requestTagsForUser( + $this->featureContext->getBaseUrl(), + $this->featureContext->getActualUsername($user), + $this->featureContext->getPasswordForUser($user), + $this->featureContext->getStepLineRef(), + $withGroups + ); + } + + /** + * @param string $user + * @param string $tagDisplayName + * @param bool $withGroups + * + * @return SimpleXMLElement|null + */ + public function requestTagByDisplayName( + string $user, + string $tagDisplayName, + bool $withGroups = false + ):?SimpleXMLElement { + $tagList = $this->requestTagsForUser($user, $withGroups); + + $tagData = $tagList->xpath( + "//d:prop//oc:display-name[text() = '$tagDisplayName']/.." + ); + if (isset($tagData[0])) { + return $tagData[0]; + } + + return null; + } + + /** + * @Then the following tags should exist for the administrator + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingTagsShouldExistForTheAdministrator(TableNode $table):void { + $this->theFollowingTagsShouldExistForUser( + $this->featureContext->getAdminUsername(), + $table + ); + } + + /** + * @Then the following tags should exist for the user + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingTagsShouldExistForTheUser(TableNode $table):void { + $this->theFollowingTagsShouldExistForUser( + $this->featureContext->getCurrentUser(), + $table + ); + } + + /** + * @Then the following tags should exist for user :user + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingTagsShouldExistForUser(string $user, TableNode $table):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumns($table, ['name', 'type']); + foreach ($table->getHash() as $row) { + $tagData = $this->requestTagByDisplayName($user, $row['name']); + if ($tagData === null) { + Assert::fail( + "tag ${row['name']} is not in propfind answer" + ); + } else { + $this->assertTypeOfTag($tagData, $row['type']); + } + } + } + + /** + * @Then tag :tagDisplayName should not exist for user :user + * + * @param string $tagDisplayName + * @param string $user + * + * @return void + */ + public function tagShouldNotExistForUser(string $tagDisplayName, string $user):void { + $tagData = $this->requestTagByDisplayName($user, $tagDisplayName); + Assert::assertNull( + $tagData, + "tag $tagDisplayName is in propfind answer" + ); + } + + /** + * @Then tag :tagDisplayName should not exist for the administrator + * + * @param string $tagDisplayName + * + * @return void + * @throws Exception + */ + public function theFollowingTagsShouldNotExistForTheAdministrator(string $tagDisplayName):void { + $this->tagShouldNotExistForUser( + $tagDisplayName, + $this->featureContext->getAdminUsername() + ); + } + + /** + * @Then tag :tagDisplayName should not exist for the user + * + * @param string $tagDisplayName + * + * @return void + * @throws Exception + */ + public function theFollowingTagsShouldNotExistForTheUser(string $tagDisplayName):void { + $this->tagShouldNotExistForUser( + $tagDisplayName, + $this->featureContext->getCurrentUser() + ); + } + + /** + * @Then /^the user (should|should not) be able to assign the "([^"]*)" tag with name "([^"]*)"$/ + * + * @param string $shouldOrNot should or should not + * @param string $type + * @param string $tagDisplayName + * + * @return void + * @throws Exception + */ + public function theUserCanAssignTheTag( + string $shouldOrNot, + string $type, + string $tagDisplayName + ):void { + $this->userCanAssignTheTag( + $this->featureContext->getCurrentUser(), + $shouldOrNot, + $type, + $tagDisplayName + ); + } + + /** + * @Then /^user "([^"]*)" (should|should not) be able to assign the "([^"]*)" tag with name "([^"]*)"$/ + * + * @param string $user + * @param string $shouldOrNot should or should not + * @param string $type + * @param string $tagDisplayName + * + * @return void + * @throws Exception + */ + public function userCanAssignTheTag( + string $user, + string $shouldOrNot, + string $type, + string $tagDisplayName + ):void { + $tagData = $this->requestTagByDisplayName($user, $tagDisplayName); + $this->assertTypeOfTag($tagData, $type); + if ($shouldOrNot === 'should') { + $expected = 'true'; + $errorMessage = 'Tag cannot be assigned by user but should'; + } elseif ($shouldOrNot === 'should not') { + $expected = 'false'; + $errorMessage = 'Tag can be assigned by user but should not'; + } else { + throw new Exception( + 'Invalid condition, must be "should" or "should not"' + ); + } + $canAssign = $tagData->xpath(".//oc:can-assign[text() = '$expected']"); + Assert::assertArrayHasKey(0, $canAssign, $errorMessage); + } + + /** + * @Then the :type tag with name :tagName should have the groups :groups + * + * @param string $type + * @param string $tagName + * @param string $groups list of groups separated by "|" + * + * @return void + * @throws Exception + */ + public function theTagHasGroup(string $type, string $tagName, string $groups):void { + $tagData = $this->requestTagByDisplayName( + $this->featureContext->getAdminUsername(), + $tagName, + true + ); + Assert::assertNotNull( + $tagData, + "Tag $tagName wasn't found for admin user" + ); + $this->assertTypeOfTag($tagData, $type); + $groupsOfTag = $tagData->xpath(".//oc:groups"); + Assert::assertArrayHasKey( + 0, + $groupsOfTag, + "cannot find oc:groups element" + ); + Assert::assertEquals( + $groupsOfTag[0], + $groups, + "Tag has groups '{$groupsOfTag[0]}' instead of the expected '$groups'" + ); + } + + /** + * @Then :count tags should exist for user :user + * + * @param int $count + * @param string $user + * + * @return void + * @throws Exception + */ + public function tagsShouldExistForUser(int $count, string $user):void { + Assert::assertEquals( + (int) $count, + \count($this->requestTagsforUser($user)), + __METHOD__ + . " Expected $count tags, got " + . \count($this->requestTagsForUser($user)) + ); + } + + /** + * @param string $name + * + * @return int + */ + public function findTagIdByName(string $name):int { + $tagData = $this->requestTagByDisplayName( + $this->featureContext->getAdminUsername(), + $name + ); + return TagsHelper::getTagIdFromTagData($tagData); + } + + /** + * @param string $user + * @param string $tagDisplayName + * @param string $propertyName + * @param string $propertyValue + * + * @return ResponseInterface + */ + private function sendProppatchToSystemtags( + string $user, + string $tagDisplayName, + string $propertyName, + string $propertyValue + ):ResponseInterface { + $renamedUser = $this->featureContext->getActualUsername($user); + $tagID = $this->findTagIdByName($tagDisplayName); + $response = WebDavHelper::proppatch( + $this->featureContext->getBaseUrl(), + $renamedUser, + $this->featureContext->getPasswordForUser($user), + "/systemtags/$tagID", + $propertyName, + $propertyValue, + $this->featureContext->getStepLineRef(), + "oc='http://owncloud.org/ns'", + $this->featureContext->getDavPathVersion("systemtags"), + "systemtags" + ); + $this->featureContext->setResponse($response); + return $response; + } + + /** + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function editTagWithNameAndSetNameUsingWebDAVAPIAsAdmin(string $oldName, string $newName):void { + $this->editTagName( + $this->featureContext->getAdminUsername(), + $oldName, + $newName + ); + } + + /** + * @When the administrator edits the tag with name :oldName and sets its name to :newName using the WebDAV API + * + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function theAdministratorEditsTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $oldName, string $newName):void { + $this->editTagWithNameAndSetNameUsingWebDAVAPIAsAdmin( + $oldName, + $newName + ); + } + + /** + * @Given the administrator has edited the tag with name :oldName and set its name to :newName + * + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEditedTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $oldName, string $newName):void { + $this->editTagWithNameAndSetNameUsingWebDAVAPIAsAdmin( + $oldName, + $newName + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function editTagWithNameAndSetNameUsingWebDAVAPIAsCurrentUser(string $oldName, string $newName):void { + $this->editTagName( + $this->featureContext->getCurrentUser(), + $oldName, + $newName + ); + } + + /** + * @When the user edits the tag with name :oldName and sets its name to :newName using the WebDAV API + * + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function theUserEditsTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $oldName, string $newName):void { + $this->editTagWithNameAndSetNameUsingWebDAVAPIAsCurrentUser( + $oldName, + $newName + ); + } + + /** + * @Given the user has edited the tag with name :oldName and set its name to :newName + * + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function theUserHasEditedTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $oldName, string $newName):void { + $this->editTagWithNameAndSetNameUsingWebDAVAPIAsCurrentUser( + $oldName, + $newName + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function editTagName(string $user, string $oldName, string $newName):void { + $this->sendProppatchToSystemtags($user, $oldName, 'display-name', $newName); + } + + /** + * @When user :user edits the tag with name :oldName and sets its name to :newName using the WebDAV API + * + * @param string $user + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function userEditsTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $user, string $oldName, string $newName):void { + $this->editTagName( + $user, + $oldName, + $newName + ); + } + + /** + * @Given user :user has edited the tag with name :oldName and set its name to :newName + * + * @param string $user + * @param string $oldName + * @param string $newName + * + * @return void + * @throws Exception + */ + public function userHasEditedTheTagWithNameAndSetItsNameToUsingTheWebDAVAPI(string $user, string $oldName, string $newName):void { + $this->editTagName( + $user, + $oldName, + $newName + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function editTagWithNameAndSetsGroupsUsingWebDAVAPIAsAdmin(string $oldName, string $groups):void { + $this->editTagGroups( + $this->featureContext->getAdminUsername(), + $oldName, + $groups + ); + } + + /** + * @When the administrator edits the tag with name :oldName and sets its groups to :groups using the WebDAV API + * + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theAdministratorEditsTheTagWithNameAndSetsItsGroupsToUsingTheWebDAVAPI(string $oldName, string $groups):void { + $this->editTagWithNameAndSetsGroupsUsingWebDAVAPIAsAdmin( + $oldName, + $groups + ); + } + + /** + * @Given the administrator has edited the tag with name :oldName and set its groups to :groups + * + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEditedTheTagWithNameAndSetsItsGroupsToUsingTheWebDAVAPI(string $oldName, string $groups):void { + $this->editTagWithNameAndSetsGroupsUsingWebDAVAPIAsAdmin( + $oldName, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function editTagWithNameAndSetGroupsUsingWebDAVAPIAsCurrentUser(string $oldName, string $groups):void { + $this->editTagGroups( + $this->featureContext->getCurrentUser(), + $oldName, + $groups + ); + } + + /** + * @When the user edits the tag with name :oldName and sets its groups to :groups using the WebDAV API + * + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theUserEditsTheTagWithNameAndSetsItsGroupsToUsingTheWebDAVAPI(string $oldName, string $groups):void { + $this->editTagWithNameAndSetGroupsUsingWebDAVAPIAsCurrentUser( + $oldName, + $groups + ); + } + + /** + * @Given the user has edited the tag with name :oldName and set its groups to :groups + * + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function theUserHasEditedTheTagWithNameAndSetsItsGroupsToUsingTheWebDAVAPI(string $oldName, string $groups):void { + $this->editTagWithNameAndSetGroupsUsingWebDAVAPIAsCurrentUser( + $oldName, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function editTagGroups(string $user, string $oldName, string $groups):void { + $user = $this->featureContext->getActualUsername($user); + $this->sendProppatchToSystemtags($user, $oldName, 'groups', $groups); + } + + /** + * @When user :user edits the tag with name :oldName and sets its groups to :groups using the WebDAV API + * + * @param string $user + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function userEditsTheTagWithNameAndSetsItsGroupsToUsingTheWebDavApi(string $user, string $oldName, string $groups):void { + $this->editTagGroups( + $user, + $oldName, + $groups + ); + } + + /** + * @Given user :user has edited the tag with name :oldName and set its groups to :groups + * + * @param string $user + * @param string $oldName + * @param string $groups + * + * @return void + * @throws Exception + */ + public function userHasEditedTheTagWithNameAndSetsItsGroupsToUsingTheWebDavApi(string $user, string $oldName, string $groups):void { + $this->editTagGroups( + $user, + $oldName, + $groups + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $user + * @param string $name tag name + * + * @return void + */ + public function deleteTag(string $user, string $name):void { + $tagID = $this->findTagIdByName($name); + $response = TagsHelper::deleteTag( + $this->featureContext->getBaseUrl(), + $this->featureContext->getActualUsername($user), + $this->featureContext->getPasswordForUser($user), + $tagID, + $this->featureContext->getStepLineRef(), + $this->featureContext->getDavPathVersion('systemtags') + ); + $this->featureContext->setResponse($response); + if ($response->getStatusCode() === 204) { + unset($this->createdTags[$tagID]); + } + } + + /** + * @When user :user deletes the tag with name :name using the WebDAV API + * + * @param string $user + * @param string $name + * + * @return void + */ + public function userDeletesTag(string $user, string $name):void { + $this->deleteTag( + $user, + $name + ); + } + + /** + * @param string $name + * + * @return void + */ + public function deleteTagAsCurrentUser(string $name):void { + $this->userDeletesTag( + $this->featureContext->getCurrentUser(), + $name + ); + } + + /** + * @When the user deletes the tag with name :name using the WebDAV API + * + * @param string $name + * + * @return void + */ + public function theUserDeletesTagWithName(string $name):void { + $this->deleteTagAsCurrentUser($name); + } + + /** + * @param string $name + * + * @return void + */ + public function deleteTagAsAdmin(string $name):void { + $this->userDeletesTag( + $this->featureContext->getAdminUsername(), + $name + ); + } + + /** + * @When the administrator deletes the tag with name :name using the WebDAV API + * + * @param string $name + * + * @return void + */ + public function theAdministratorDeletesTagWithName(string $name):void { + $this->deleteTagAsAdmin($name); + } + + /** + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * @param string|null $fileOwner + * + * @return void + * @throws Exception + */ + private function tag( + string $taggingUser, + string $tagName, + string $fileName, + ?string $fileOwner = null + ):void { + if ($fileOwner === null) { + $fileOwner = $taggingUser; + } + + $response = TagsHelper::tag( + $this->featureContext->getBaseUrl(), + $taggingUser, + $this->featureContext->getPasswordForUser($taggingUser), + $tagName, + $fileName, + $this->featureContext->getStepLineRef(), + $fileOwner, + $this->featureContext->getPasswordForUser($fileOwner), + $this->featureContext->getDavPathVersion('systemtags'), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword() + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $user + * @param string $fileName + * @param string|null $sharingUser + * + * @return SimpleXMLElement + * @throws Exception + */ + private function requestTagsForFile(string $user, string $fileName, ?string $sharingUser = null):SimpleXMLElement { + $user = $this->featureContext->getActualUsername($user); + if ($sharingUser !== null) { + $sharingUser = $this->featureContext->getActualUsername($sharingUser); + $fileID = $this->featureContext->getFileIdForPath($sharingUser, $fileName); + } else { + $fileID = $this->featureContext->getFileIdForPath($user, $fileName); + } + $properties = [ + 'oc:id', + 'oc:display-name', + 'oc:user-visible', + 'oc:user-assignable', + 'oc:user-editable', + 'oc:can-assign' + ]; + $appPath = '/systemtags-relations/files/'; + $fullPath = $appPath . $fileID; + $response = WebDavHelper::propfind( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + $fullPath, + $properties, + $this->featureContext->getStepLineRef(), + '1', + 'systemtags', + $this->featureContext->getDavPathVersion('systemtags') + ); + $this->featureContext->setResponse($response); + return HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + } + + /** + * @param string $adminOrUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function addTagToFileFolderAsAdminOrUser( + string $adminOrUser, + string $tagName, + string $fileName + ):void { + $adminOrUser = $this->featureContext->getActualUsername($adminOrUser); + if ($adminOrUser === 'administrator') { + $taggingUser = $this->featureContext->getAdminUsername(); + } else { + $taggingUser = $this->featureContext->getCurrentUser(); + } + $this->addTagToFileFolder($taggingUser, $tagName, $fileName); + } + + /** + * @When /^the (administrator|user) adds tag "([^"]*)" to (?:file|folder) "([^"]*)" using the WebDAV API$/ + * + * @param string $adminOrUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function theUserOrAdministratorAddsTagToFileFolder( + string $adminOrUser, + string $tagName, + string $fileName + ):void { + $this->addTagToFileFolderAsAdminOrUser( + $adminOrUser, + $tagName, + $fileName + ); + } + + /** + * @Given /^the (administrator|user) has added tag "([^"]*)" to (?:file|folder) "([^"]*)"$/ + * @Given /^the (administrator|user) has toggled tag "([^"]*)" to (?:file|folder) "([^"]*)"$/ + * + * @param string $adminOrUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function theUserOrAdministratorHasAddedTagToFileFolder( + string $adminOrUser, + string $tagName, + string $fileName + ):void { + $this->addTagToFileFolderAsAdminOrUser( + $adminOrUser, + $tagName, + $fileName + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function addTagToFileFolder( + string $taggingUser, + string $tagName, + string $fileName + ):void { + $taggingUser = $this->featureContext->getActualUsername($taggingUser); + $this->tag($taggingUser, $tagName, $fileName); + } + + /** + * @When /^user "([^"]*)" adds tag "([^"]*)" to (?:file|folder) "([^"]*)" using the WebDAV API$/ + * + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function userAddsTagToFileFolder( + string $taggingUser, + string $tagName, + string $fileName + ):void { + $this->addTagToFileFolder( + $taggingUser, + $tagName, + $fileName + ); + } + + /** + * @Given /^user "([^"]*)" has added tag "([^"]*)" to (?:file|folder) "([^"]*)"$/ + * + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * + * @return void + * @throws Exception + */ + public function userHasAddedTagToFileFolder( + string $taggingUser, + string $tagName, + string $fileName + ):void { + $this->addTagToFileFolder( + $taggingUser, + $tagName, + $fileName + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * @param string $sharingUser + * + * @return void + * @throws Exception + */ + public function addTagToResourceSharedByUser( + string $taggingUser, + string $tagName, + string $fileName, + string $sharingUser + ):void { + $taggingUser = $this->featureContext->getActualUsername($taggingUser); + $sharingUser = $this->featureContext->getActualUsername($sharingUser); + $this->tag( + $taggingUser, + $tagName, + $fileName, + $sharingUser + ); + } + + /** + * @When /^user "([^"]*)" adds tag "([^"]*)" to (?:file|folder) "([^"]*)" (?:shared|owned) by user "([^"]*)" using the WebDAV API$/ + * + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * @param string $sharingUser + * + * @return void + * @throws Exception + */ + public function userAddsTagToSharedBy( + string $taggingUser, + string $tagName, + string $fileName, + string $sharingUser + ):void { + $this->addTagToResourceSharedByUser( + $taggingUser, + $tagName, + $fileName, + $sharingUser + ); + } + + /** + * @Given /^user "([^"]*)" has added tag "([^"]*)" to (?:file|folder) "([^"]*)" (?:shared|owned) by user "([^"]*)"$/ + * + * @param string $taggingUser + * @param string $tagName + * @param string $fileName + * @param string $sharingUser + * + * @return void + * @throws Exception + */ + public function userHasAddedTagToSharedBy( + string $taggingUser, + string $tagName, + string $fileName, + string $sharingUser + ):void { + $this->addTagToResourceSharedByUser( + $taggingUser, + $tagName, + $fileName, + $sharingUser + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the HTTP status when user "([^"]*)" requests tags for (?:file|folder|entry) "([^"]*)" (?:shared|owned) by user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $fileName + * @param string $sharingUser + * @param string $status + * + * @return void + * @throws Exception + */ + public function theHttpStatusWhenUserRequestsTagsForEntryOwnedByShouldBe( + string $user, + string $fileName, + string $sharingUser, + string $status + ):void { + $this->requestTagsForFile($user, $fileName, $sharingUser); + $actualStatus = $this->featureContext->getResponse()->getStatusCode(); + Assert::assertEquals( + $status, + $actualStatus, + __METHOD__ + . " Expected status is '$status' but got '$actualStatus'" + ); + } + + /** + * @When /^user "([^"]*)" requests tags for (?:file|folder|entry) "([^"]*)" (?:shared|owned) by user "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileName + * @param string $sharingUser + * + * @return void + * @throws Exception + */ + public function whenUserRequestsTagsForEntryOwnedByAnotherUser( + string $user, + string $fileName, + string $sharingUser + ):void { + $this->requestTagsForFile($user, $fileName, $sharingUser); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" (?:shared|owned) by the (administrator|user) should have the following tags$/ + * + * @param string $fileName + * @param string $adminOrUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function sharedByTheUserOrAdminHasTheFollowingTags( + string $fileName, + string $adminOrUser, + TableNode $table + ):void { + if ($adminOrUser === 'user') { + $sharingUser = $this->featureContext->getCurrentUser(); + } else { + $sharingUser = $this->featureContext->getAdminUsername(); + } + $this->sharedByHasTheFollowingTags($fileName, $sharingUser, $table); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" (?:shared|owned) by user "([^"]*)" should have the following tags$/ + * + * @param string $fileName + * @param string $sharingUser + * @param TableNode $table - Table containing tags. Should have two columns ('name' and 'type') + * e.g. + * | name | type | + * | tag1 | normal | + * | tag2 | static | + * + * @return bool + * @throws Exception + */ + public function sharedByHasTheFollowingTags( + string $fileName, + string $sharingUser, + TableNode $table + ):bool { + $xml = $this->requestTagsForFile($sharingUser, $fileName); + $tagList = $xml->xpath("//d:prop"); + $found = false; + $this->featureContext->verifyTableNodeColumns($table, ['name', 'type']); + foreach ($table->getHash() as $row) { + $found = false; + foreach ($tagList as $tagData) { + $displayName = $tagData->xpath(".//oc:display-name"); + Assert::assertArrayHasKey( + 0, + $displayName, + "cannot find 'oc:display-name' property" + ); + if ($displayName[0]->__toString() === $row['name']) { + $found = true; + $this->assertTypeOfTag($tagData, $row['type']); + break; + } + } + if ($found === false) { + Assert::fail( + "tag ${row['name']} is not in propfind answer" + ); + } + } + return $found; + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" should have the following tags for the (administrator|user)$/ + * + * @param string $fileName + * @param string $adminOrUser + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function fileHasTheFollowingTagsForUserOrAdministrator( + string $fileName, + string $adminOrUser, + TableNode $table + ):void { + if ($adminOrUser === 'administrator') { + $user = $this->featureContext->getAdminUsername(); + } else { + $user = $this->featureContext->getCurrentUser(); + } + $this->fileHasTheFollowingTagsForUser($fileName, $user, $table); + } + + /** + * @Then file :fileName should have the following tags for user :user + * + * @param string $fileName + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function fileHasTheFollowingTagsForUser( + string $fileName, + string $user, + TableNode $table + ):void { + $this->sharedByHasTheFollowingTags($fileName, $user, $table); + } + + /** + * @Then /^(?:file|folder|entry) "([^"]*)" (?:shared|owned) by "([^"]*)" should have no tags$/ + * + * @param string $fileName + * @param string $sharingUser + * + * @return void + * @throws Exception + */ + public function sharedByHasNoTags(string $fileName, string $sharingUser):void { + $sharingUser = $this->featureContext->getActualUsername($sharingUser); + $responseXml = $this->requestTagsForFile($sharingUser, $fileName); + $tagList = $responseXml->xpath("//d:prop"); + // The array of tags has a single "empty" item at the start. + // If there are no tags, then the array should have just this + // one entry. + $numTags = \count($tagList) - 1; + Assert::assertEquals( + 0, + $numTags, + "Expected no tags for '$fileName', but got '" . $numTags . "' tags" + ); + } + + /** + * @Then file/folder :fileName should have no tags for user :user + * @Then entry :fileName should have no tags for user :user + * @Then /^(?:file|folder|entry) "([^"]*)" should have no tags for the (administrator|user)?$/ + * + * @param string $fileName + * @param string|null $adminOrUser + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function fileHasNoTagsForUser(string $fileName, ?string $adminOrUser = null, ?string $user = null):void { + if ($user === null) { + if ($adminOrUser === 'administrator') { + $user = $this->featureContext->getAdminUsername(); + } else { + $user = $this->featureContext->getCurrentUser(); + } + } + $this->sharedByHasNoTags($fileName, $user); + } + + /** + * @param string $untaggingUser + * @param string $tagName + * @param string $fileName + * @param string $fileOwner + * + * @return void + */ + private function untag(string $untaggingUser, string $tagName, string $fileName, string $fileOwner):void { + $untaggingUser = $this->featureContext->getActualUsername($untaggingUser); + $fileOwner = $this->featureContext->getActualUsername($fileOwner); + $fileID = $this->featureContext->getFileIdForPath($fileOwner, $fileName); + $tagID = $this->findTagIdByName($tagName); + $path = "/systemtags-relations/files/$fileID/$tagID"; + $response = $this->featureContext->makeDavRequest( + $untaggingUser, + "DELETE", + $path, + null, + null, + "uploads", + (string)$this->featureContext->getDavPathVersion('systemtags') + ); + $this->featureContext->setResponse($response); + } + + /** + * @param string $user + * @param string $tagName + * @param string $fileName + * + * @return void + */ + public function removeTagFromFile(string $user, string $tagName, string $fileName):void { + $this->untag($user, $tagName, $fileName, $user); + } + + /** + * @When user :user removes tag :tagName from file :fileName using the WebDAV API + * + * @param string $user + * @param string $tagName + * @param string $fileName + * + * @return void + */ + public function userRemovesTagFromFile(string $user, string $tagName, string $fileName):void { + $this->removeTagFromFile( + $user, + $tagName, + $fileName + ); + } + + /** + * @param string $user + * @param string $tagName + * @param string $fileName + * @param string $shareUser + * + * @return void + */ + public function removeTagFromFileSharedByUser( + string $user, + string $tagName, + string $fileName, + string $shareUser + ):void { + $this->untag( + $user, + $tagName, + $fileName, + $shareUser + ); + } + + /** + * @When user :user removes tag :tagName from file :fileName shared by :shareUser using the WebDAV API + * + * @param string $user + * @param string $tagName + * @param string $fileName + * @param string $shareUser + * + * @return void + */ + public function userRemovesTagFromFileSharedBy( + string $user, + string $tagName, + string $fileName, + string $shareUser + ):void { + $this->removeTagFromFileSharedByUser( + $user, + $tagName, + $fileName, + $shareUser + ); + } + + /** + * @param string $tagName + * @param string $fileName + * @param string $shareUser + * + * @return void + */ + public function removeTagFromFileSharedByUserAsAdminUsingWebDavApi( + string $tagName, + string $fileName, + string $shareUser + ):void { + $admin = $this->featureContext->getAdminUsername(); + $this->removeTagFromFileSharedByUser($admin, $tagName, $fileName, $shareUser); + } + + /** + * @When the administrator removes tag :tagName from file :fileName shared by :shareUser using the WebDAV API + * + * @param string $tagName + * @param string $fileName + * @param string $shareUser + * + * @return void + */ + public function theAdministratorRemovesTheTagFromFileSharedByUsingTheWebdavApi( + string $tagName, + string $fileName, + string $shareUser + ):void { + $this->removeTagFromFileSharedByUserAsAdminUsingWebDavApi( + $tagName, + $fileName, + $shareUser + ); + } + + /** + * search resources with tags using the REPORT webDAV method + * + * @param string $user + * @param TableNode $tagNames + * + * @return void + * @throws Exception + */ + public function searchForTagsOfFileWithReportUsingWebDAVApi(string $user, TableNode $tagNames):void { + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->verifyTableNodeColumnsCount($tagNames, 1); + $tagNames = $tagNames->getRows(); + $baseUrl = $this->featureContext->getBaseUrl(); + $password = $this->featureContext->getPasswordForUser($user); + $createdTagsArray = $this->getListOfCreatedTags(); + $createdTagIds = []; + $createdTagNames = []; + foreach ($createdTagsArray as $tagId => $tagArray) { + \array_push($createdTagIds, $tagId); + \array_push($createdTagNames, $tagArray['name']); + } + $body = "\n" . + " \n" . + " \n"; + foreach ($tagNames as $tagName) { + $found = \in_array($tagName[0], $createdTagNames); + if ($found) { + $index = \array_search($tagName[0], $createdTagNames); + $body .= + " $createdTagIds[$index]\n"; + } else { + throw new Error( + "Expected: Tag with name $tagName[0] to be in created list, but not found!" . + "List of created Tags: " . \implode(",", $createdTagNames) + ); + } + } + $body .= + " \n" . + " "; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "REPORT", + null, + null, + $this->featureContext->getStepLineRef(), + $body, + 2 + ); + $this->featureContext->setResponse($response); + $responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $responseXmlObject->registerXPathNamespace('d', 'DAV:'); + $responseXmlObject->registerXPathNamespace('oc', 'http://owncloud.org/ns'); + $this->featureContext->setResponseXmlObject($responseXmlObject); + } + + /** + * @When user :user searches for resources tagged with all of the following tags using the webDAV API + * + * @param string $user + * @param TableNode $tagNames + * + * @return void + * @throws Exception + */ + public function userSearchesForFollowingTagsUsingWebDAVApi(string $user, TableNode $tagNames):void { + $this->searchForTagsOfFileWithReportUsingWebDAVApi( + $user, + $tagNames + ); + } + + /** + * @When user :user searches for resources tagged with tag :tagName using the webDAV API + * + * @param string $user + * @param string $tagName + * + * @return void + * @throws Exception + */ + public function userSearchesForTagUsingWebDavAPI(string $user, string $tagName):void { + $tagName = new TableNode([[$tagName]]); + $this->searchForTagsOfFileWithReportUsingWebDAVApi($user, $tagName); + } + + /** + * @Then /^as user "([^"]*)" the response should (not |)contain (file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $shouldOrNot + * @param string $fileOrFolder + * @param string $path + * + * @return void + */ + public function asUserFileShouldBeTaggedWithTagName(string $user, string $shouldOrNot, string $fileOrFolder, string $path):void { + $user = $this->featureContext->getActualUsername($user); + $expected = ($shouldOrNot === ""); + $responseResourcesArray = $this->featureContext->findEntryFromReportResponse($user); + if ($expected) { + Assert::assertTrue( + \in_array($path, $responseResourcesArray), + "Expected: $fileOrFolder $path to be present in last response, but not found! \n" . + "Resource from response: " . \implode(",", $responseResourcesArray) + ); + } else { + Assert::assertFalse( + \in_array($path, $responseResourcesArray), + "Expected: $fileOrFolder $path not to be present in last response, but found present! \n" . + "Resource from response: " . \implode(",", $responseResourcesArray) + ); + } + } + + /** + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function cleanupTags():void { + $this->featureContext->authContext->deleteTokenAuthEnforcedAfterScenario(); + foreach ($this->createdTags as $tagID => $tag) { + TagsHelper::deleteTag( + $this->featureContext->getBaseUrl(), + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $tagID, + $this->featureContext->getStepLineRef(), + 2 + ); + } + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/TrashbinContext.php b/tests/acceptance/features/bootstrap/TrashbinContext.php new file mode 100644 index 00000000000..ed0e90c8142 --- /dev/null +++ b/tests/acceptance/features/bootstrap/TrashbinContext.php @@ -0,0 +1,1182 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use Psr\Http\Message\ResponseInterface; +use TestHelpers\HttpRequestHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * Trashbin context + */ +class TrashbinContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var OccContext + */ + private $occContext; + + /** + * @When user :user empties the trashbin using the trashbin API + * + * @param string|null $user user + * + * @return ResponseInterface + */ + public function emptyTrashbin(?string $user):ResponseInterface { + $user = $this->featureContext->getActualUsername($user); + $davPathVersion = $this->featureContext->getDavPathVersion(); + $response = WebDavHelper::makeDavRequest( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + 'DELETE', + null, + [], + $this->featureContext->getStepLineRef(), + null, + $davPathVersion, + 'trash-bin' + ); + + $this->featureContext->setResponse($response); + return $response; + } + + /** + * @Given user :user has emptied the trashbin + * + * @param string $user user + * + * @return void + */ + public function userHasEmptiedTrashbin(string $user):void { + $response = $this->emptyTrashbin($user); + + Assert::assertEquals( + 204, + $response->getStatusCode(), + __METHOD__ . " Expected status code was '204' but got '" . $response->getStatusCode() . "'" + ); + } + + /** + * Get files list from the response from trashbin api + * + * @param SimpleXMLElement|null $responseXml + * + * @return array + */ + public function getTrashbinContentFromResponseXml(?SimpleXMLElement $responseXml): array { + $xmlElements = $responseXml->xpath('//d:response'); + $files = \array_map( + static function (SimpleXMLElement $element) { + $href = $element->xpath('./d:href')[0]; + + $propStats = $element->xpath('./d:propstat'); + $successPropStat = \array_filter( + $propStats, + static function (SimpleXMLElement $propStat) { + $status = $propStat->xpath('./d:status'); + return (string) $status[0] === 'HTTP/1.1 200 OK'; + } + ); + if (isset($successPropStat[0])) { + $successPropStat = $successPropStat[0]; + + $name = $successPropStat->xpath('./d:prop/oc:trashbin-original-filename'); + $mtime = $successPropStat->xpath('./d:prop/oc:trashbin-delete-timestamp'); + $resourcetype = $successPropStat->xpath('./d:prop/d:resourcetype'); + if (\array_key_exists(0, $resourcetype) && ($resourcetype[0]->asXML() === "")) { + $collection[0] = true; + } else { + $collection[0] = false; + } + $originalLocation = $successPropStat->xpath('./d:prop/oc:trashbin-original-location'); + } else { + $name = []; + $mtime = []; + $collection = []; + $originalLocation = []; + } + + return [ + 'href' => (string) $href, + 'name' => isset($name[0]) ? (string) $name[0] : null, + 'mtime' => isset($mtime[0]) ? (string) $mtime[0] : null, + 'collection' => isset($collection[0]) ? $collection[0] : false, + 'original-location' => isset($originalLocation[0]) ? (string) $originalLocation[0] : null + ]; + }, + $xmlElements + ); + + return $files; + } + + /** + * List the top of the trashbin folder for a user + * + * @param string|null $user user + * @param string $depth + * + * @return array response + * @throws Exception + */ + public function listTopOfTrashbinFolder(?string $user, string $depth = "infinity"):array { + $password = $this->featureContext->getPasswordForUser($user); + $davPathVersion = $this->featureContext->getDavPathVersion(); + $response = WebDavHelper::listFolder( + $this->featureContext->getBaseUrl(), + $user, + $password, + "", + $depth, + $this->featureContext->getStepLineRef(), + [ + 'oc:trashbin-original-filename', + 'oc:trashbin-original-location', + 'oc:trashbin-delete-timestamp', + 'd:getlastmodified' + ], + 'trash-bin', + $davPathVersion + ); + $this->featureContext->setResponse($response); + $responseXml = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + + $this->featureContext->setResponseXmlObject($responseXml); + $files = $this->getTrashbinContentFromResponseXml($responseXml); + // filter root element + $files = \array_filter( + $files, + static function ($element) use ($user) { + return ($element['href'] !== "/remote.php/dav/trash-bin/$user/"); + } + ); + return $files; + } + + /** + * List trashbin folder + * + * @param string|null $user user + * @param string $depth + * + * @return array of all the items in the trashbin of the user + * @throws Exception + */ + public function listTrashbinFolder(?string $user, string $depth = "1"):array { + return $this->listTrashbinFolderCollection( + $user, + "", + $depth + ); + } + + /** + * List a collection in the trashbin + * + * @param string|null $user user + * @param string|null $collectionPath the string of ids of the folder and sub-folders + * @param string $depth + * @param int $level + * + * @return array response + * @throws Exception + */ + public function listTrashbinFolderCollection(?string $user, ?string $collectionPath = "", string $depth = "1", int $level = 1):array { + // $collectionPath should be some list of file-ids like 2147497661/2147497662 + // or the empty string, which will list the whole trashbin from the top. + $collectionPath = \trim($collectionPath, "/"); + $password = $this->featureContext->getPasswordForUser($user); + $davPathVersion = $this->featureContext->getDavPathVersion(); + $response = WebDavHelper::listFolder( + $this->featureContext->getBaseUrl(), + $user, + $password, + $collectionPath, + $depth, + $this->featureContext->getStepLineRef(), + [ + 'oc:trashbin-original-filename', + 'oc:trashbin-original-location', + 'oc:trashbin-delete-timestamp', + 'd:resourcetype', + 'd:getlastmodified' + ], + 'trash-bin', + $davPathVersion + ); + $responseXml = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ . " $collectionPath" + ); + + $files = $this->getTrashbinContentFromResponseXml($responseXml); + // filter out the collection itself, we only want to return the members + $files = \array_filter( + $files, + static function ($element) use ($user, $collectionPath) { + $path = $collectionPath; + if ($path !== "") { + $path = $path . "/"; + } + return ($element['href'] !== "/remote.php/dav/trash-bin/$user/$path"); + } + ); + + foreach ($files as $file) { + // check for unexpected/invalid href values and fail early in order to + // avoid "common" situations that could cause infinite recursion. + $trashbinRef = $file["href"]; + $trimmedTrashbinRef = \trim($trashbinRef, "/"); + $expectedStart = "remote.php/dav/trash-bin/$user"; + $expectedStartLength = \strlen($expectedStart); + if ((\substr($trimmedTrashbinRef, 0, $expectedStartLength) !== $expectedStart) + || (\strlen($trimmedTrashbinRef) === $expectedStartLength) + ) { + // A top href (maybe without even the username) has been returned + // in the response. That should never happen, or have been filtered out + // by code above. + throw new Exception( + __METHOD__ . " Error: unexpected href in trashbin propfind at level $level: '$trashbinRef'" + ); + } + if ($file["collection"]) { + $trimmedHref = \trim($trashbinRef, "/"); + $explodedHref = \explode("/", $trimmedHref); + $trashbinId = $collectionPath . "/" . end($explodedHref); + $nextFiles = $this->listTrashbinFolderCollection( + $user, + $trashbinId, + $depth, + $level + 1 + ); + // filter the collection element. We only want the members. + $nextFiles = \array_filter( + $nextFiles, + static function ($element) use ($user, $trashbinRef) { + return ($element['href'] !== $trashbinRef); + } + ); + \array_push($files, ...$nextFiles); + } + } + return $files; + } + + /** + * @When user :user lists the resources in the trashbin with depth :depth using the WebDAV API + * + * @param string $user + * @param string $depth + * + * @return void + * @throws Exception + */ + public function userGetsFilesInTheTrashbinWithDepthUsingTheWebdavApi(string $user, string $depth):void { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + $this->listTopOfTrashbinFolder($user, $depth); + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + } + + /** + * @Then the trashbin DAV response should not contain these nodes + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theTrashbinDavResponseShouldNotContainTheseNodes(TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['name']); + $responseXml = $this->featureContext->getResponseXmlObject(); + $files = $this->getTrashbinContentFromResponseXml($responseXml); + + foreach ($table->getHash() as $row) { + $path = trim((string)$row['name'], "/"); + foreach ($files as $file) { + if (trim((string)$file['original-location'], "/") === $path) { + throw new Exception("file $path was not expected in trashbin response but was found"); + } + } + } + } + + /** + * @Then the trashbin DAV response should contain these nodes + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theTrashbinDavResponseShouldContainTheseNodes(TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['name']); + $responseXml = $this->featureContext->getResponseXmlObject(); + + $files = $this->getTrashbinContentFromResponseXml($responseXml); + + foreach ($table->getHash() as $row) { + $path = trim($row['name'], "/"); + $found = false; + foreach ($files as $file) { + if (trim((string)$file['original-location'], "/") === $path) { + $found = true; + break; + } + } + if (!$found) { + throw new Exception("file $path was expected in trashbin response but was not found"); + } + } + } + + /** + * Send a webdav request to list the trashbin content + * + * @param string $user user + * @param string|null $asUser - To send request as another user + * @param string|null $password + * + * @return void + * @throws Exception + */ + public function sendTrashbinListRequest(string $user, ?string $asUser = null, ?string $password = null):void { + $asUser = $asUser ?? $user; + $password = $password ?? $this->featureContext->getPasswordForUser($asUser); + $davPathVersion = $this->featureContext->getDavPathVersion(); + $response = WebDavHelper::propfind( + $this->featureContext->getBaseUrl(), + $asUser, + $password, + null, + [ + 'oc:trashbin-original-filename', + 'oc:trashbin-original-location', + 'oc:trashbin-delete-timestamp', + 'd:getlastmodified' + ], + $this->featureContext->getStepLineRef(), + '1', + 'trash-bin', + $davPathVersion, + $user + ); + $this->featureContext->setResponse($response); + try { + $responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $this->featureContext->setResponseXmlObject($responseXmlObject); + } catch (Exception $e) { + $this->featureContext->clearResponseXmlObject(); + } + } + + /** + * @When user :asUser tries to list the trashbin content for user :user + * + * @param string $asUser + * @param string $user + * + * @return void + * @throws Exception + */ + public function userTriesToListTheTrashbinContentForUser(string $asUser, string $user) { + $user = $this->featureContext->getActualUsername($user); + $asUser = $this->featureContext->getActualUsername($asUser); + $this->sendTrashbinListRequest($user, $asUser); + } + + /** + * @When user :asUser tries to list the trashbin content for user :user using password :password + * + * @param string $asUser + * @param string $user + * @param string $password + * + * @return void + * @throws Exception + */ + public function userTriesToListTheTrashbinContentForUserUsingPassword(string $asUser, string $user, string $password):void { + $this->sendTrashbinListRequest($user, $asUser, $password); + } + + /** + * @Then the last webdav response should contain the following elements + * + * @param TableNode $elements + * + * @return void + */ + public function theLastWebdavResponseShouldContainFollowingElements(TableNode $elements):void { + $files = $this->getTrashbinContentFromResponseXml($this->featureContext->getResponseXmlObject()); + if (!($elements instanceof TableNode)) { + throw new InvalidArgumentException( + '$expectedElements has to be an instance of TableNode' + ); + } + $elementRows = $elements->getHash(); + foreach ($elementRows as $expectedElement) { + $found = false; + $expectedPath = $expectedElement['path']; + foreach ($files as $file) { + if (\ltrim($expectedPath, "/") === \ltrim($file['original-location'], "/")) { + $found = true; + break; + } + } + Assert::assertTrue($found, "$expectedPath expected to be listed in response but not found"); + } + } + + /** + * @Then the last webdav response should not contain the following elements + * + * @param TableNode $elements + * + * @return void + * @throws Exception + */ + public function theLastWebdavResponseShouldNotContainFollowingElements(TableNode $elements):void { + $files = $this->getTrashbinContentFromResponseXml($this->featureContext->getResponseXmlObject()); + + // 'user' is also allowed in the table even though it is not used anywhere + // This for better readability in feature files + $this->featureContext->verifyTableNodeColumns($elements, ['path'], ['path', 'user']); + $elementRows = $elements->getHash(); + foreach ($elementRows as $expectedElement) { + $notFound = true; + $expectedPath = "/" . \ltrim($expectedElement['path'], "/"); + foreach ($files as $file) { + // Allow the table of expected elements to have entries that do + // not have to specify the "implied" leading slash, or have multiple + // leading slashes, to make scenario outlines more flexible + if ($expectedPath === $file['original-location']) { + $notFound = false; + } + } + Assert::assertTrue($notFound, "$expectedPath expected not to be listed in response but found"); + } + } + + /** + * @When user :asUser tries to delete the file with original path :path from the trashbin of user :user using the trashbin API + * + * @param string $asUser + * @param string $path + * @param string $user + * + * @return void + * @throws Exception + */ + public function userTriesToDeleteFromTrashbinOfUser(string $asUser, string $path, string $user):void { + $numItemsDeleted = $this->tryToDeleteFileFromTrashbin($user, $path, $asUser); + } + + /** + * @When user :asUser tries to delete the file with original path :path from the trashbin of user :user using the password :password and the trashbin API + * + * @param string|null $asUser + * @param string|null $path + * @param string|null $user + * @param string|null $password + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function userTriesToDeleteFromTrashbinOfUserUsingPassword(?string $asUser, ?string $path, ?string $user, ?string $password):void { + $user = $this->featureContext->getActualUsername($user); + $asUser = $this->featureContext->getActualUsername($asUser); + $numItemsDeleted = $this->tryToDeleteFileFromTrashbin($user, $path, $asUser, $password); + } + + /** + * @When user :asUser tries to restore the file with original path :path from the trashbin of user :user using the trashbin API + * + * @param string|null $asUser + * @param string|null $path + * @param string|null $user + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function userTriesToRestoreFromTrashbinOfUser(?string $asUser, ?string $path, ?string $user):void { + $user = $this->featureContext->getActualUsername($user); + $asUser = $this->featureContext->getActualUsername($asUser); + $this->restoreElement($user, $path, null, true, $asUser); + } + + /** + * @When user :asUser tries to restore the file with original path :path from the trashbin of user :user using the password :password and the trashbin API + * + * @param string|null $asUser + * @param string|null $path + * @param string|null $user + * @param string|null $password + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function userTriesToRestoreFromTrashbinOfUserUsingPassword(?string $asUser, ?string $path, ?string $user, ?string $password):void { + $asUser = $this->featureContext->getActualUsername($asUser); + $user = $this->featureContext->getActualUsername($user); + $this->restoreElement($user, $path, null, true, $asUser, $password); + } + + /** + * converts the trashItemHRef from //remote.php/dav/trash-bin/// to /trash-bin// + * + * @param string $href + * + * @return string + */ + private function convertTrashbinHref(string $href):string { + $trashItemHRef = \trim($href, '/'); + $trashItemHRef = \strstr($trashItemHRef, '/trash-bin'); + $trashItemHRef = \trim($trashItemHRef, '/'); + $parts = \explode('/', $trashItemHRef); + $decodedParts = \array_slice($parts, 2); + return '/' . \join('/', $decodedParts); + } + + /** + * @When /^user "([^"]*)" tries to delete the (?:file|folder|entry) with original path "([^"]*)" from the trashbin using the trashbin API$/ + * + * @param string|null $user + * @param string|null $originalPath + * @param string|null $asUser + * @param string|null $password + * + * @return int the number of items that were matched and requested for delete + * @throws JsonException + * @throws Exception + */ + public function tryToDeleteFileFromTrashbin(?string $user, ?string $originalPath, ?string $asUser = null, ?string $password = null):int { + $user = $this->featureContext->getActualUsername($user); + $asUser = $asUser ?? $user; + $listing = $this->listTrashbinFolder($user); + $originalPath = \trim($originalPath, '/'); + $numItemsDeleted = 0; + + foreach ($listing as $entry) { + if ($entry['original-location'] === $originalPath) { + $trashItemHRef = $this->convertTrashbinHref($entry['href']); + $response = $this->featureContext->makeDavRequest( + $asUser, + 'DELETE', + $trashItemHRef, + [], + null, + 'trash-bin', + null, + false, + $password, + [], + $user + ); + $this->featureContext->setResponse($response); + $numItemsDeleted++; + } + } + + return $numItemsDeleted; + } + + /** + * @When /^user "([^"]*)" deletes the (?:file|folder|entry) with original path "([^"]*)" from the trashbin using the trashbin API$/ + * + * @param string $user + * @param string $originalPath + * + * @return void + * @throws Exception + */ + public function deleteFileFromTrashbin(string $user, string $originalPath):void { + $numItemsDeleted = $this->tryToDeleteFileFromTrashbin($user, $originalPath); + + Assert::assertEquals( + 1, + $numItemsDeleted, + "Expected to delete exactly one item from the trashbin but $numItemsDeleted were deleted" + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" deletes the following (?:files|folders|entries) with original path from the trashbin$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function deleteFollowingFilesFromTrashbin(string $user, TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $path) { + $this->deleteFileFromTrashbin($user, $path["path"]); + + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @Then /^as "([^"]*)" (?:file|folder|entry) "([^"]*)" should exist in the trashbin$/ + * + * @param string|null $user + * @param string|null $path + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function asFileOrFolderExistsInTrash(?string $user, ?string $path):void { + $user = $this->featureContext->getActualUsername($user); + $path = \trim($path, '/'); + $sections = \explode('/', $path, 2); + + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + + $firstEntry = $this->findFirstTrashedEntry($user, \trim($sections[0], '/')); + + Assert::assertNotNull( + $firstEntry, + "The first trash entry was not found while looking for trashbin entry '$path' of user '$user'" + ); + + if (\count($sections) !== 1) { + // TODO: handle deeper structures + $listing = $this->listTrashbinFolderCollection($user, \basename(\rtrim($firstEntry['href'], '/'))); + } + + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + + // query was on the main element ? + if (\count($sections) === 1) { + // already found, return + return; + } + + $checkedName = \basename($path); + + $found = false; + foreach ($listing as $entry) { + if ($entry['name'] === $checkedName) { + $found = true; + break; + } + } + + Assert::assertTrue( + $found, + __METHOD__ + . " Could not find expected resource '$path' in the trash" + ); + } + + /** + * Function to check if an element is in the trashbin + * + * @param string|null $user + * @param string|null $originalPath + * + * @return bool + * @throws Exception + */ + private function isInTrash(?string $user, ?string $originalPath):bool { + $techPreviewHadToBeEnabled = $this->occContext->enableDAVTechPreview(); + $res = $this->featureContext->getResponse(); + $listing = $this->listTrashbinFolder($user); + + $this->featureContext->setResponse($res); + if ($techPreviewHadToBeEnabled) { + $this->occContext->disableDAVTechPreview(); + } + + // we don't care if the test step writes a leading "/" or not + $originalPath = \ltrim($originalPath, '/'); + + foreach ($listing as $entry) { + if ($entry['original-location'] !== null && \ltrim($entry['original-location'], '/') === $originalPath) { + return true; + } + } + return false; + } + + /** + * @param string $user + * @param string $trashItemHRef + * @param string $destinationPath + * @param string|null $asUser - To send request as another user + * @param string|null $password + * + * @return ResponseInterface + */ + private function sendUndeleteRequest(string $user, string $trashItemHRef, string $destinationPath, ?string $asUser = null, ?string $password = null):ResponseInterface { + $asUser = $asUser ?? $user; + $destinationPath = \trim($destinationPath, '/'); + $destinationValue = $this->featureContext->getBaseUrl() . "/remote.php/dav/files/$user/$destinationPath"; + + $trashItemHRef = $this->convertTrashbinHref($trashItemHRef); + $headers['Destination'] = $destinationValue; + $response = $this->featureContext->makeDavRequest( + $asUser, + 'MOVE', + $trashItemHRef, + $headers, + null, + 'trash-bin', + '2', + false, + $password, + [], + $user + ); + $this->featureContext->setResponse($response); + return $response; + } + + /** + * @param string $user + * @param string $originalPath + * @param string|null $destinationPath + * @param bool $throwExceptionIfNotFound + * @param string|null $asUser - To send request as another user + * @param string|null $password + * + * @return ResponseInterface|null + * @throws Exception + */ + private function restoreElement(string $user, string $originalPath, ?string $destinationPath = null, bool $throwExceptionIfNotFound = true, ?string $asUser = null, ?string $password = null):?ResponseInterface { + $asUser = $asUser ?? $user; + $listing = $this->listTrashbinFolder($user); + $originalPath = \trim($originalPath, '/'); + if ($destinationPath === null) { + $destinationPath = $originalPath; + } + foreach ($listing as $entry) { + if ($entry['original-location'] === $originalPath) { + return $this->sendUndeleteRequest( + $user, + $entry['href'], + $destinationPath, + $asUser, + $password + ); + } + } + // The requested element to restore was not even in the trashbin. + // Throw an exception, because there was not any API call, and so there + // is also no up-to-date response to examine in later test steps. + if ($throwExceptionIfNotFound) { + throw new \Exception( + __METHOD__ + . " cannot restore from trashbin because no element was found for user $user at original path $originalPath" + ); + } + return null; + } + + /** + * @When user :user restores the folder with original path :originalPath without specifying the destination using the trashbin API + * + * @param $user string + * @param $originalPath string + * + * @return ResponseInterface + * @throws Exception + */ + public function restoreFileWithoutDestination(string $user, string $originalPath):ResponseInterface { + $asUser = $asUser ?? $user; + $listing = $this->listTrashbinFolder($user); + $originalPath = \trim($originalPath, '/'); + + foreach ($listing as $entry) { + if ($entry['original-location'] === $originalPath) { + $trashItemHRef = $this->convertTrashbinHref($entry['href']); + $response = $this->featureContext->makeDavRequest( + $asUser, + 'MOVE', + $trashItemHRef, + [], + null, + 'trash-bin' + ); + $this->featureContext->setResponse($response); + // this gives empty response in ocis + try { + $responseXml = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + $this->featureContext->setResponseXmlObject($responseXml); + } catch (Exception $e) { + } + + return $response; + } + } + throw new \Exception( + __METHOD__ + . " cannot restore from trashbin because no element was found for user $user at original path $originalPath" + ); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" if the file is also in the trashbin should be "([^"]*)" otherwise "([^"]*)"$/ + * + * Note: this is a special step for an unusual bug combination. + * Delete it when the bug is fixed and the step is no longer needed. + * + * @param string|null $fileName + * @param string|null $user + * @param string|null $content + * @param string|null $alternativeContent + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function contentOfFileForUserIfAlsoInTrashShouldBeOtherwise( + ?string $fileName, + ?string $user, + ?string $content, + ?string $alternativeContent + ):void { + $isInTrash = $this->isInTrash($user, $fileName); + $user = $this->featureContext->getActualUsername($user); + $this->featureContext->downloadFileAsUserUsingPassword($user, $fileName); + if ($isInTrash) { + $this->featureContext->downloadedContentShouldBe($content); + } else { + $this->featureContext->downloadedContentShouldBe($alternativeContent); + } + } + + /** + * @When /^user "([^"]*)" tries to restore the (?:file|folder|entry) with original path "([^"]*)" using the trashbin API$/ + * + * @param string $user + * @param string $originalPath + * + * @return void + * @throws Exception + */ + public function userTriesToRestoreElementInTrash(string $user, string $originalPath):void { + $this->restoreElement($user, $originalPath, null, false); + } + + /** + * @When /^user "([^"]*)" restores the (?:file|folder|entry) with original path "([^"]*)" using the trashbin API$/ + * + * @param string|null $user + * @param string $originalPath + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function elementInTrashIsRestored(?string $user, string $originalPath):void { + $user = $this->featureContext->getActualUsername($user); + $this->restoreElement($user, $originalPath); + } + + /** + * @When /^user "([^"]*)" restores the following (?:files|folders|entries) with original path$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userRestoresFollowingFiles(string $user, TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $originalPath) { + $this->elementInTrashIsRestored($user, $originalPath["path"]); + + $this->featureContext->pushToLastStatusCodesArrays(); + } + } + + /** + * @Given /^user "([^"]*)" has restored the (?:file|folder|entry) with original path "([^"]*)"$/ + * + * @param string $user + * @param string $originalPath + * + * @return void + * @throws Exception + */ + public function elementInTrashHasBeenRestored(string $user, string $originalPath):void { + $this->restoreElement($user, $originalPath); + if ($this->isInTrash($user, $originalPath)) { + throw new Exception("File previously located at $originalPath is still in the trashbin"); + } + } + + /** + * @When /^user "([^"]*)" restores the (?:file|folder|entry) with original path "([^"]*)" to "([^"]*)" using the trashbin API$/ + * + * @param string|null $user + * @param string|null $originalPath + * @param string|null $destinationPath + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function userRestoresTheFileWithOriginalPathToUsingTheTrashbinApi( + ?string $user, + ?string $originalPath, + ?string $destinationPath + ):void { + $user = $this->featureContext->getActualUsername($user); + $this->restoreElement($user, $originalPath, $destinationPath); + } + + /** + * @Then /^as "([^"]*)" the (?:file|folder|entry) with original path "([^"]*)" should exist in the trashbin$/ + * + * @param string|null $user + * @param string|null $originalPath + * + * @return void + * @throws JsonException + * @throws Exception + */ + public function elementIsInTrashCheckingOriginalPath( + ?string $user, + ?string $originalPath + ):void { + $user = $this->featureContext->getActualUsername($user); + Assert::assertTrue( + $this->isInTrash($user, $originalPath), + "File previously located at $originalPath wasn't found in the trashbin of user $user" + ); + } + + /** + * @Then /^as "([^"]*)" the (?:file|folder|entry) with original path "([^"]*)" should not exist in the trashbin/ + * + * @param string|null $user + * @param string $originalPath + * + * @return void + * @throws Exception + */ + public function elementIsNotInTrashCheckingOriginalPath( + ?string $user, + string $originalPath + ):void { + $user = $this->featureContext->getActualUsername($user); + Assert::assertFalse( + $this->isInTrash($user, $originalPath), + "File previously located at $originalPath was found in the trashbin of user $user" + ); + } + + /** + * @Then /^as "([^"]*)" the (?:files|folders|entries) with following original paths should not exist in the trashbin$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function followingElementsAreNotInTrashCheckingOriginalPath( + string $user, + TableNode $table + ):void { + $this->featureContext->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash($table); + + foreach ($paths as $originalPath) { + $this->elementIsNotInTrashCheckingOriginalPath($user, $originalPath["path"]); + } + } + + /** + * @Then /^as "([^"]*)" the (?:files|folders|entries) with following original paths should exist in the trashbin$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function followingElementsAreInTrashCheckingOriginalPath( + string $user, + TableNode $table + ):void { + $this->featureContext->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash($table); + + foreach ($paths as $originalPath) { + $this->elementIsInTrashCheckingOriginalPath($user, $originalPath["path"]); + } + } + + /** + * Finds the first trashed entry matching the given name + * + * @param string $user + * @param string $name + * + * @return array|null real entry name with timestamp suffix or null if not found + * @throws Exception + */ + private function findFirstTrashedEntry(string $user, string $name):?array { + $listing = $this->listTrashbinFolder($user); + + foreach ($listing as $entry) { + if ($entry['name'] === $name) { + return $entry; + } + } + + return null; + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + $this->occContext = $environment->getContext('OccContext'); + } + + /** + * @Then /^the deleted (?:file|folder) "([^"]*)" should have the correct deletion mtime in the response$/ + * + * @param string $resource file or folder in trashbin + * + * @return void + */ + public function theDeletedFileFolderShouldHaveCorrectDeletionMtimeInTheResponse(string $resource):void { + $files = $this->getTrashbinContentFromResponseXml( + $this->featureContext->getResponseXmlObject() + ); + + $found = false; + $expectedMtime = $this->featureContext->getLastUploadDeleteTime(); + $responseMtime = ''; + + foreach ($files as $file) { + if (\ltrim((string)$resource, "/") === \ltrim((string)$file['original-location'], "/")) { + $responseMtime = $file['mtime']; + $mtime_difference = \abs((int)\trim((string)$expectedMtime) - (int)\trim($responseMtime)); + + if ($mtime_difference <= 2) { + $found = true; + break; + } + } + } + Assert::assertTrue( + $found, + "$resource expected to be listed in response with mtime '$expectedMtime' but found '$responseMtime'" + ); + } + + /** + * @Given the administrator has set the following file extensions to be skipped from the trashbin + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetFollowingFileExtensionsToBeSkippedFromTrashbin(TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['extension']); + foreach ($table->getHash() as $idx => $row) { + $this->featureContext->runOcc(['config:system:set', 'trashbin_skip_extensions', $idx, '--value=' . $row['extension']]); + } + } + + /** + * @Given the administrator has set the following directories to be skipped from the trashbin + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetFollowingDirectoriesToBeSkippedFromTrashbin(TableNode $table):void { + $this->featureContext->verifyTableNodeColumns($table, ['directory']); + foreach ($table->getHash() as $idx => $row) { + $this->featureContext->runOcc(['config:system:set', 'trashbin_skip_directories', $idx, '--value=' . $row['directory']]); + } + } + + /** + * @Given the administrator has set the trashbin skip size threshold to :threshold + * + * @param string $threshold + * + * @return void + * @throws Exception + */ + public function theAdministratorHasSetTrashbinSkipSizethreshold(string $threshold) { + $this->featureContext->runOcc(['config:system:set', 'trashbin_skip_size_threshold', '--value=' . $threshold]); + } +} diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php new file mode 100644 index 00000000000..d966840cd86 --- /dev/null +++ b/tests/acceptance/features/bootstrap/WebDav.php @@ -0,0 +1,5527 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Exception\BadResponseException; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Ring\Exception\ConnectException; +use PHPUnit\Framework\Assert; +use Psr\Http\Message\ResponseInterface; +use GuzzleHttp\Stream\StreamInterface; +use TestHelpers\OcisHelper; +use TestHelpers\OcsApiHelper; +use TestHelpers\SetupHelper; +use TestHelpers\UploadHelper; +use TestHelpers\WebDavHelper; +use TestHelpers\HttpRequestHelper; +use TestHelpers\Asserts\WebDav as WebDavAssert; + +/** + * WebDav functions + */ +trait WebDav { + /** + * @var string + */ + private $davPath = "remote.php/webdav"; + + /** + * @var boolean + */ + private $usingOldDavPath = true; + + /** + * @var boolean + */ + private $usingSpacesDavPath = false; + + /** + * @var ResponseInterface[] + */ + private $uploadResponses; + + /** + * @var integer + */ + private $storedFileID = null; + + /** + * @var int + */ + private $lastUploadDeleteTime = null; + + /** + * a variable that contains the DAV path without "remote.php/(web)dav" + * when setting $this->davPath directly by usingDavPath() + * + * @var string + */ + private $customDavPath = null; + + private $previousAsyncSetting = null; + + private $previousDavSlowdownSetting = null; + + /** + * @var int + */ + private $currentDavSlowdownSettingSeconds = 0; + + /** + * response content parsed from XML to an array + * + * @var array + */ + private $responseXml = []; + + /** + * add resource created by admin in an array + * This array is used while cleaning up the resource created by admin during test run + * As of now it tracks only for (files|folder) creation + * This can be expanded and modified to track other actions like (upload, deleted ..) + * + * @var array + */ + private $adminResources = []; + + /** + * response content parsed into a SimpleXMLElement + * + * @var SimpleXMLElement + */ + private $responseXmlObject; + + private $httpRequestTimeout = 0; + + private $chunkingToUse = null; + + /** + * The ability to do requests with depth infinity is disabled by default. + * This remembers when the setting dav.propfind.depth_infinity has been + * enabled, so that test code can make use of it as appropriate. + * + * @var bool + */ + private $davPropfindDepthInfinityEnabled = false; + + /** + * @return void + */ + public function davPropfindDepthInfinityEnabled():void { + $this->davPropfindDepthInfinityEnabled = true; + } + + /** + * @return void + */ + public function davPropfindDepthInfinityDisabled():void { + $this->davPropfindDepthInfinityEnabled = false; + } + + /** + * @return bool + */ + public function davPropfindDepthInfinityIsEnabled():bool { + return $this->davPropfindDepthInfinityEnabled; + } + + /** + * @param int $lastUploadDeleteTime + * + * @return void + */ + public function setLastUploadDeleteTime(int $lastUploadDeleteTime):void { + $this->lastUploadDeleteTime = $lastUploadDeleteTime; + } + + /** + * @return number + */ + public function getLastUploadDeleteTime():int { + return $this->lastUploadDeleteTime; + } + + /** + * @return SimpleXMLElement|null + */ + public function getResponseXmlObject():?SimpleXMLElement { + return $this->responseXmlObject; + } + + /** + * @param SimpleXMLElement $responseXmlObject + * + * @return void + */ + public function setResponseXmlObject(SimpleXMLElement $responseXmlObject):void { + $this->responseXmlObject = $responseXmlObject; + } + + /** + * @return void + */ + public function clearResponseXmlObject():void { + $this->responseXmlObject = null; + } + + /** + * + * @return string the etag or an empty string if the getetag property does not exist + */ + public function getEtagFromResponseXmlObject():string { + $xmlObject = $this->getResponseXmlObject(); + $xmlPart = $xmlObject->xpath("//d:prop/d:getetag"); + if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) { + return ''; + } + return $xmlPart[0]->__toString(); + } + + /** + * + * @param string|null $eTag if null then get eTag from response XML object + * + * @return boolean + */ + public function isEtagValid(?string $eTag = null):bool { + if ($eTag === null) { + $eTag = $this->getEtagFromResponseXmlObject(); + } + if (\preg_match("/^\"[a-f0-9:\.]{1,32}\"$/", $eTag) + ) { + return true; + } else { + return false; + } + } + + /** + * @param array $responseXml + * + * @return void + */ + public function setResponseXml(array $responseXml):void { + $this->responseXml = $responseXml; + } + + /** + * @param ResponseInterface[] $uploadResponses + * + * @return void + */ + public function setUploadResponses(array $uploadResponses):void { + $this->uploadResponses = $uploadResponses; + } + + /** + * @Given /^using DAV path "([^"]*)"$/ + * + * @param string $davPath + * + * @return void + */ + public function usingDavPath(string $davPath):void { + $this->davPath = $davPath; + $this->customDavPath = \preg_replace( + "/remote\.php\/(web)?dav\//", + "", + $davPath + ); + } + + /** + * @return string + */ + public function getOldDavPath():string { + return "remote.php/webdav"; + } + + /** + * @return string + */ + public function getNewDavPath():string { + return "remote.php/dav"; + } + + /** + * @return string + */ + public function getSpacesDavPath():string { + return "dav/spaces"; + } + + /** + * @Given /^using (old|new|spaces) (?:dav|DAV) path$/ + * + * @param string $davChoice + * + * @return void + */ + public function usingOldOrNewDavPath(string $davChoice):void { + if ($davChoice === 'old') { + $this->usingOldDavPath(); + } elseif ($davChoice === 'new') { + $this->usingNewDavPath(); + } else { + $this->usingSpacesDavPath(); + } + } + + /** + * Select the old DAV path as the default for later scenario steps + * + * @return void + */ + public function usingOldDavPath():void { + $this->davPath = $this->getOldDavPath(); + $this->usingOldDavPath = true; + $this->customDavPath = null; + } + + /** + * Select the new DAV path as the default for later scenario steps + * + * @return void + */ + public function usingNewDavPath():void { + $this->davPath = $this->getNewDavPath(); + $this->usingOldDavPath = false; + $this->customDavPath = null; + $this->usingSpacesDavPath = false; + } + + /** + * Select the spaces dav path as the default for later scenario steps + * + * @return void + */ + public function usingSpacesDavPath():void { + $this->davPath = $this->getSpacesDavPath(); + $this->usingOldDavPath = false; + $this->customDavPath = null; + $this->usingSpacesDavPath = true; + } + + /** + * gives the DAV path of a file including the subfolder of the webserver + * e.g. when the server runs in `http://localhost/owncloud/` + * this function will return `owncloud/remote.php/webdav/prueba.txt` + * + * @param string $user + * + * @return string + * @throws GuzzleException + */ + public function getFullDavFilesPath(string $user):string { + $spaceId = null; + if ($this->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES) { + $spaceId = (WebDavHelper::$SPACE_ID_FROM_OCIS) ? WebDavHelper::$SPACE_ID_FROM_OCIS : WebDavHelper::getPersonalSpaceIdForUser( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + $this->getStepLineRef() + ); + } + $path = $this->getBasePath() . "/" . + WebDavHelper::getDavPath($user, $this->getDavPathVersion(), "files", $spaceId); + $path = WebDavHelper::sanitizeUrl($path); + return \ltrim($path, "/"); + } + + /** + * @param string $token + * @param string $type + * + * @return string + */ + public function getPublicLinkDavPath(string $token, string $type):string { + $path = $this->getBasePath() . "/" . + WebDavHelper::getDavPath($token, $this->getDavPathVersion(), $type); + $path = WebDavHelper::sanitizeUrl($path); + return \ltrim($path, "/"); + } + + /** + * Select a suitable DAV path version number. + * Some endpoints have only existed since a certain point in time, so for + * those make sure to return a DAV path version that works for that endpoint. + * Otherwise return the currently selected DAV path version. + * + * @param string|null $for the category of endpoint that the DAV path will be used for + * + * @return int DAV path version (1, 2 or 3) selected, or appropriate for the endpoint + */ + public function getDavPathVersion(?string $for = null):?int { + if ($this->usingSpacesDavPath) { + return WebDavHelper::DAV_VERSION_SPACES; + } + if ($for === 'systemtags') { + // systemtags only exists since DAV v2 + return WebDavHelper::DAV_VERSION_NEW; + } + if ($for === 'file_versions') { + // file_versions only exists since DAV v2 + return WebDavHelper::DAV_VERSION_NEW; + } + if ($this->usingOldDavPath === true) { + return WebDavHelper::DAV_VERSION_OLD; + } else { + return WebDavHelper::DAV_VERSION_NEW; + } + } + + /** + * Select a suitable DAV path. + * Some endpoints have only existed since a certain point in time, so for + * those make sure to return a DAV path that works for that endpoint. + * Otherwise return the currently selected DAV path. + * + * @param string|null $for the category of endpoint that the DAV path will be used for + * + * @return string DAV path selected, or appropriate for the endpoint + */ + public function getDavPath(?string $for = null):string { + $davPathVersion = $this->getDavPathVersion($for); + if ($davPathVersion === WebDavHelper::DAV_VERSION_OLD) { + return $this->getOldDavPath(); + } + + if ($davPathVersion === WebDavHelper::DAV_VERSION_NEW) { + return $this->getNewDavPath(); + } + + return $this->getSpacesDavPath(); + } + + /** + * @param string|null $user + * @param string|null $method + * @param string|null $path + * @param array|null $headers + * @param StreamInterface|null $body + * @param string|null $type + * @param string|null $davPathVersion + * @param bool $stream Set to true to stream a response rather + * than download it all up-front. + * @param string|null $password + * @param array|null $urlParameter + * @param string|null $doDavRequestAsUser + * + * @return ResponseInterface + * @throws GuzzleException|JsonException + */ + public function makeDavRequest( + ?string $user, + ?string $method, + ?string $path, + ?array $headers, + $body = null, + ?string $type = "files", + ?string $davPathVersion = null, + bool $stream = false, + ?string $password = null, + ?array $urlParameter = [], + ?string $doDavRequestAsUser = null + ):ResponseInterface { + $user = $this->getActualUsername($user); + if ($this->customDavPath !== null) { + $path = $this->customDavPath . $path; + } + + if ($davPathVersion === null) { + $davPathVersion = $this->getDavPathVersion(); + } else { + $davPathVersion = (int) $davPathVersion; + } + + if ($password === null) { + $password = $this->getPasswordForUser($user); + } + + return WebDavHelper::makeDavRequest( + $this->getBaseUrl(), + $user, + $password, + $method, + $path, + $headers, + $this->getStepLineRef(), + $body, + $davPathVersion, + $type, + null, + "basic", + $stream, + $this->httpRequestTimeout, + null, + $urlParameter, + $doDavRequestAsUser + ); + } + + /** + * @param string $user + * @param string|null $path + * @param string|null $doDavRequestAsUser + * @param string|null $width + * @param string|null $height + * + * @return void + */ + public function downloadPreviews(string $user, ?string $path, ?string $doDavRequestAsUser, ?string $width, ?string $height):void { + $user = $this->getActualUsername($user); + $doDavRequestAsUser = $this->getActualUsername($doDavRequestAsUser); + $urlParameter = [ + 'x' => $width, + 'y' => $height, + 'forceIcon' => '0', + 'preview' => '1' + ]; + $this->response = $this->makeDavRequest( + $user, + "GET", + $path, + [], + null, + "files", + '2', + false, + null, + $urlParameter, + $doDavRequestAsUser + ); + } + + /** + * @Then the number of versions should be :arg1 + * + * @param int $number + * + * @return void + * @throws Exception + */ + public function theNumberOfVersionsShouldBe(int $number):void { + $resXml = $this->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->getResponse(), + __METHOD__ + ); + $this->setResponseXmlObject($resXml); + } + $xmlPart = $resXml->xpath("//d:getlastmodified"); + $actualNumber = \count($xmlPart); + Assert::assertEquals( + $number, + $actualNumber, + "Expected number of versions was '$number', but got '$actualNumber'" + ); + } + + /** + * @Then the number of etag elements in the response should be :number + * + * @param int $number + * + * @return void + * @throws Exception + */ + public function theNumberOfEtagElementInTheResponseShouldBe(int $number):void { + $resXml = $this->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->getResponse(), + __METHOD__ + ); + } + $xmlPart = $resXml->xpath("//d:getetag"); + $actualNumber = \count($xmlPart); + Assert::assertEquals( + $number, + $actualNumber, + "Expected number of etag elements was '$number', but got '$actualNumber'" + ); + } + + /** + * @Given /^the administrator has (enabled|disabled) async operations$/ + * + * @param string $enabledOrDisabled + * + * @return void + * @throws Exception + */ + public function triggerAsyncUpload(string $enabledOrDisabled):void { + $switch = ($enabledOrDisabled !== "disabled"); + if ($switch) { + $value = 'true'; + } else { + $value = 'false'; + } + if ($this->previousAsyncSetting === null) { + $previousAsyncSetting = SetupHelper::runOcc( + ['config:system:get', 'dav.enable.async'], + $this->getStepLineRef() + )['stdOut']; + $this->previousAsyncSetting = \trim($previousAsyncSetting); + } + $this->runOcc( + [ + 'config:system:set', + 'dav.enable.async', + '--type', + 'boolean', + '--value', + $value + ] + ); + } + + /** + * @Given the HTTP-Request-timeout is set to :seconds seconds + * + * @param int $timeout + * + * @return void + */ + public function setHttpTimeout(int $timeout):void { + $this->httpRequestTimeout = (int) $timeout; + } + + /** + * @Given the :method DAV requests are slowed down by :seconds seconds + * + * @param string $method + * @param int $seconds + * + * @return void + * @throws Exception + */ + public function slowdownDavRequests(string $method, int $seconds):void { + if ($this->previousDavSlowdownSetting === null) { + $previousDavSlowdownSetting = SetupHelper::runOcc( + ['config:system:get', 'dav.slowdown'], + $this->getStepLineRef() + )['stdOut']; + $this->previousDavSlowdownSetting = \trim($previousDavSlowdownSetting); + } + OcsApiHelper::sendRequest( + $this->getBaseUrl(), + $this->getAdminUsername(), + $this->getAdminPassword(), + "PUT", + "/apps/testing/api/v1/davslowdown/$method/$seconds", + $this->getStepLineRef() + ); + $this->currentDavSlowdownSettingSeconds = $seconds; + } + + /** + * Wait for possible slowed-down DAV requests to finish + * + * @return void + */ + public function waitForDavRequestsToFinish():void { + if ($this->currentDavSlowdownSettingSeconds > 0) { + // There could be a slowed-down request still happening on the server + // Wait just-in-case so that we do not accidentally have an effect on + // the next scenario. + \sleep($this->currentDavSlowdownSettingSeconds); + } + } + + /** + * @param string $user + * @param string $fileDestination + * + * @return string + * @throws GuzzleException + */ + public function destinationHeaderValue(string $user, string $fileDestination):string { + $spaceId = $this->getPersonalSpaceIdForUser($user); + $fullUrl = $this->getBaseUrl() . '/' . + WebDavHelper::getDavPath($user, $this->getDavPathVersion(), "files", $spaceId); + return \rtrim($fullUrl, '/') . '/' . \ltrim($fileDestination, '/'); + } + + /** + * @Given /^user "([^"]*)" has moved (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/ + * + * @param string|null $user + * @param string|null $fileSource + * @param string|null $fileDestination + * + * @return void + */ + public function userHasMovedFile( + ?string $user, + ?string $fileSource, + ?string $fileDestination + ):void { + $user = $this->getActualUsername($user); + $headers['Destination'] = $this->destinationHeaderValue( + $user, + $fileDestination + ); + $this->response = $this->makeDavRequest( + $user, + "MOVE", + $fileSource, + $headers + ); + $expectedStatusCode = 201; + $actualStatusCode = $this->response->getStatusCode(); + Assert::assertEquals( + $expectedStatusCode, + $actualStatusCode, + __METHOD__ . " Failed moving resource '$fileSource' to '$fileDestination'." + . " Expected status code was '$expectedStatusCode' but got '$actualStatusCode'" + ); + } + + /** + * @Given /^the user has moved (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/ + * + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function theUserHasMovedFile(string $fileSource, string $fileDestination):void { + $this->userHasMovedFile($this->getCurrentUser(), $fileSource, $fileDestination); + } + + /** + * @When /^user "([^"]*)" moves (file|folder|entry) "([^"]*)"\s?(asynchronously|) to these (?:filenames|foldernames|entries) using the webDAV API then the results should be as listed$/ + * + * @param string $user + * @param string $entry + * @param string $fileSource + * @param string $type "asynchronously" or empty + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userMovesEntriesUsingTheAPI( + string $user, + string $entry, + string $fileSource, + string $type, + TableNode $table + ):void { + $user = $this->getActualUsername($user); + foreach ($table->getHash() as $row) { + // Allow the "filename" column to be optionally be called "foldername" + // to help readability of scenarios that test moving folders + if (isset($row['foldername'])) { + $targetName = $row['foldername']; + } else { + $targetName = $row['filename']; + } + $this->userMovesFileUsingTheAPI( + $user, + $fileSource, + $type, + $targetName + ); + $this->theHTTPStatusCodeShouldBe( + $row['http-code'], + "HTTP status code is not the expected value while trying to move " . $targetName + ); + if ($row['exists'] === "yes") { + $this->asFileOrFolderShouldExist($user, $entry, $targetName); + // The move was successful. + // Move the file/folder back so the source file/folder exists for the next move + $this->userMovesFileUsingTheAPI( + $user, + $targetName, + '', + $fileSource + ); + } else { + $this->asFileOrFolderShouldNotExist($user, $entry, $targetName); + } + } + } + + /** + * @When /^user "([^"]*)" moves (?:file|folder|entry) "([^"]*)"\s?(asynchronously|) to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileSource + * @param string $type "asynchronously" or empty + * @param string $fileDestination + * + * @return void + * @throws JsonException + * @throws GuzzleException + */ + public function userMovesFileUsingTheAPI( + string $user, + string $fileSource, + string $type, + string $fileDestination + ):void { + $user = $this->getActualUsername($user); + $headers['Destination'] = $this->destinationHeaderValue( + $user, + $fileDestination + ); + $stream = false; + if ($type === "asynchronously") { + $headers['OC-LazyOps'] = 'true'; + if ($this->httpRequestTimeout > 0) { + //LazyOps is set and a request timeout, so we want to use stream + //to be able to read data from the request before its times out + //when doing LazyOps the server does not close the connection + //before its really finished + //but we want to read JobStatus-Location before the end of the job + //to see if it reports the correct values + $stream = true; + } + } + try { + $this->response = $this->makeDavRequest( + $user, + "MOVE", + $fileSource, + $headers, + null, + "files", + null, + $stream + ); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + $this->pushToLastHttpStatusCodesArray( + (string) $this->getResponse()->getStatusCode() + ); + } catch (ConnectException $e) { + } + } + + /** + * @When user :user moves the following file using the WebDAV API + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userMovesTheFollowingFileUsingTheWebdavApi(string $user, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["source", "destination"]); + $rows = $table->getHash(); + foreach ($rows as $row) { + $this->userMovesFileUsingTheAPI($user, $row["source"], "", $row["destination"]); + } + } + + /** + * @When /^user "([^"]*)" moves the following (?:files|folders|entries)\s?(asynchronously|) using the WebDAV API$/ + * + * @param string $user + * @param string $type "asynchronously" or empty + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userMovesFollowingFileUsingTheAPI( + string $user, + string $type, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["from", "to"]); + $paths = $table->getHash(); + + foreach ($paths as $file) { + $this->userMovesFileUsingTheAPI($user, $file['from'], $type, $file['to']); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @Then /^user "([^"]*)" should be able to rename (file|folder|entry) "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $entry + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function theUserShouldBeAbleToRenameEntryTo(string $user, string $entry, string $source, string $destination):void { + $user = $this->getActualUsername($user); + $this->asFileOrFolderShouldExist($user, $entry, $source); + $this->userMovesFileUsingTheAPI($user, $source, "", $destination); + $this->asFileOrFolderShouldNotExist($user, $entry, $source); + $this->asFileOrFolderShouldExist($user, $entry, $destination); + } + + /** + * @Then /^user "([^"]*)" should not be able to rename (file|folder|entry) "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $entry + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function theUserShouldNotBeAbleToRenameEntryTo(string $user, string $entry, string $source, string $destination):void { + $this->asFileOrFolderShouldExist($user, $entry, $source); + $this->userMovesFileUsingTheAPI($user, $source, "", $destination); + $this->asFileOrFolderShouldExist($user, $entry, $source); + $this->asFileOrFolderShouldNotExist($user, $entry, $destination); + } + + /** + * @When /^user "([^"]*)" on "(LOCAL|REMOTE)" moves (?:file|folder|entry) "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $server + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function userOnMovesFileUsingTheAPI( + string $user, + string $server, + string $fileSource, + string $fileDestination + ):void { + $previousServer = $this->usingServer($server); + $this->userMovesFileUsingTheAPI($user, $fileSource, "", $fileDestination); + $this->usingServer($previousServer); + } + + /** + * @When /^user "([^"]*)" copies (?:file|folder) "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function userCopiesFileUsingTheAPI( + string $user, + string $fileSource, + string $fileDestination + ):void { + $user = $this->getActualUsername($user); + $headers['Destination'] = $this->destinationHeaderValue( + $user, + $fileDestination + ); + $this->response = $this->makeDavRequest( + $user, + "COPY", + $fileSource, + $headers + ); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + $this->pushToLastHttpStatusCodesArray( + (string) $this->getResponse()->getStatusCode() + ); + } + + /** + * @Given /^user "([^"]*)" has copied file "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function userHasCopiedFileUsingTheAPI( + string $user, + string $fileSource, + string $fileDestination + ):void { + $this->userCopiesFileUsingTheAPI($user, $fileSource, $fileDestination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to copy file '$fileSource' to '$fileDestination' for user '$user'" + ); + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @When /^the user copies file "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function theUserCopiesFileUsingTheAPI(string $fileSource, string $fileDestination):void { + $this->userCopiesFileUsingTheAPI($this->getCurrentUser(), $fileSource, $fileDestination); + } + + /** + * @Given /^the user has copied file "([^"]*)" to "([^"]*)"$/ + * + * @param string $fileSource + * @param string $fileDestination + * + * @return void + */ + public function theUserHasCopiedFileUsingTheAPI(string $fileSource, string $fileDestination):void { + $this->theUserCopiesFileUsingTheAPI($fileSource, $fileDestination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to copy file '$fileSource' to '$fileDestination'" + ); + } + + /** + * @When /^the user downloads file "([^"]*)" with range "([^"]*)" using the WebDAV API$/ + * + * @param string $fileSource + * @param string $range + * + * @return void + */ + public function downloadFileWithRange(string $fileSource, string $range):void { + $this->userDownloadsFileWithRange( + $this->currentUser, + $fileSource, + $range + ); + } + + /** + * @When /^user "([^"]*)" downloads file "([^"]*)" with range "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileSource + * @param string $range + * + * @return void + */ + public function userDownloadsFileWithRange(string $user, string $fileSource, string $range):void { + $user = $this->getActualUsername($user); + $headers['Range'] = $range; + $this->response = $this->makeDavRequest( + $user, + "GET", + $fileSource, + $headers + ); + } + + /** + * @Then /^user "([^"]*)" using password "([^"]*)" should not be able to download file "([^"]*)"$/ + * + * @param string $user + * @param string $password + * @param string $fileName + * + * @return void + */ + public function userUsingPasswordShouldNotBeAbleToDownloadFile( + string $user, + string $password, + string $fileName + ):void { + $user = $this->getActualUsername($user); + $password = $this->getActualPassword($password); + $this->downloadFileAsUserUsingPassword($user, $fileName, $password); + Assert::assertGreaterThanOrEqual( + 400, + $this->getResponse()->getStatusCode(), + __METHOD__ + . ' download must fail' + ); + Assert::assertLessThanOrEqual( + 499, + $this->getResponse()->getStatusCode(), + __METHOD__ + . ' 4xx error expected but got status code "' + . $this->getResponse()->getStatusCode() . '"' + ); + } + + /** + * @Then /^user "([^"]*)" should not be able to download file "([^"]*)"$/ + * + * @param string $user + * @param string $fileName + * + * @return void + * @throws JsonException + */ + public function userShouldNotBeAbleToDownloadFile( + string $user, + string $fileName + ):void { + $user = $this->getActualUsername($user); + $password = $this->getPasswordForUser($user); + $this->downloadFileAsUserUsingPassword($user, $fileName, $password); + Assert::assertGreaterThanOrEqual( + 400, + $this->getResponse()->getStatusCode(), + __METHOD__ + . ' download must fail' + ); + Assert::assertLessThanOrEqual( + 499, + $this->getResponse()->getStatusCode(), + __METHOD__ + . ' 4xx error expected but got status code "' + . $this->getResponse()->getStatusCode() . '"' + ); + } + + /** + * @Then /^user "([^"]*)" should be able to access a skeleton file$/ + * + * @param string $user + * + * @return void + */ + public function userShouldBeAbleToAccessASkeletonFile(string $user):void { + $this->contentOfFileForUserShouldBePlusEndOfLine( + "textfile0.txt", + $user, + "ownCloud test text file 0" + ); + } + + /** + * @Then the size of the downloaded file should be :size bytes + * + * @param string $size + * + * @return void + */ + public function sizeOfDownloadedFileShouldBe(string $size):void { + $actualSize = \strlen((string) $this->response->getBody()); + Assert::assertEquals( + $size, + $actualSize, + "Expected size of the downloaded file was '$size' but got '$actualSize'" + ); + } + + /** + * @Then /^the downloaded content should end with "([^"]*)"$/ + * + * @param string $content + * + * @return void + */ + public function downloadedContentShouldEndWith(string $content):void { + $actualContent = \substr((string) $this->response->getBody(), -\strlen($content)); + Assert::assertEquals( + $content, + $actualContent, + "The downloaded content was expected to end with '$content', but actually ended with '$actualContent'." + ); + } + + /** + * @Then /^the downloaded content should be "([^"]*)"$/ + * + * @param string $content + * + * @return void + */ + public function downloadedContentShouldBe(string $content):void { + $this->checkDownloadedContentMatches($content); + } + + /** + * @param string $expectedContent + * @param string $extraErrorText + * + * @return void + */ + public function checkDownloadedContentMatches( + string $expectedContent, + string $extraErrorText = "" + ):void { + $actualContent = (string) $this->response->getBody(); + // For this test we really care about the content. + // A separate "Then" step can specifically check the HTTP status. + // But if the content is wrong (e.g. empty) then it is useful to + // report the HTTP status to give some clue what might be the problem. + $actualStatus = $this->response->getStatusCode(); + if ($extraErrorText !== "") { + $extraErrorText .= "\n"; + } + Assert::assertEquals( + $expectedContent, + $actualContent, + $extraErrorText . "The content was expected to be '$expectedContent', but actually is '$actualContent'. HTTP status was $actualStatus" + ); + } + + /** + * @Then the content in the response should match the following content: + * + * @param PyStringNode $content + * + * @return void + */ + public function theContentInTheResponseShouldMatchTheFollowingContent(PyStringNode $content): void { + $this->checkDownloadedContentMatches($content->getRaw()); + } + + /** + * @Then /^if the HTTP status code was "([^"]*)" then the downloaded content for multipart byterange should be:$/ + * + * @param int $statusCode + * @param PyStringNode $content + * + * @return void + * + */ + public function theDownloadedContentForMultipartByterangeShouldBe(int $statusCode, PyStringNode $content):void { + $actualStatusCode = $this->response->getStatusCode(); + if ($actualStatusCode === $statusCode) { + $actualContent = (string) $this->response->getBody(); + $pattern = ["/--\w*/", "/\s*/m"]; + $actualContent = \preg_replace($pattern, "", $actualContent); + $content = \preg_replace("/\s*/m", '', $content->getRaw()); + Assert::assertEquals( + $content, + $actualContent, + "The downloaded content was expected to be '$content', but actually is '$actualContent'. HTTP status was $actualStatusCode" + ); + } + } + + /** + * @Then /^if the HTTP status code was "([^"]*)" then the downloaded content should be "([^"]*)"$/ + * + * @param int $statusCode + * @param string $content + * + * @return void + */ + public function checkStatusCodeForDownloadedContentShouldBe(int $statusCode, string $content):void { + $actualStatusCode = $this->response->getStatusCode(); + if ($actualStatusCode === $statusCode) { + $this->downloadedContentShouldBe($content); + } + } + + /** + * @Then /^the downloaded content should be "([^"]*)" plus end-of-line$/ + * + * @param string $content + * + * @return void + */ + public function downloadedContentShouldBePlusEndOfLine(string $content):void { + $this->downloadedContentShouldBe("$content\n"); + } + + /** + * @Then /^the content of file "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileName + * @param string $content + * + * @return void + */ + public function contentOfFileShouldBe(string $fileName, string $content):void { + $this->theUserDownloadsTheFileUsingTheAPI($fileName); + $this->downloadedContentShouldBe($content); + } + + /** + * @Then /^the content of file "([^"]*)" should be:$/ + * + * @param string $fileName + * @param PyStringNode $content + * + * @return void + */ + public function contentOfFileShouldBePyString( + string $fileName, + PyStringNode $content + ):void { + $this->contentOfFileShouldBe($fileName, $content->getRaw()); + } + + /** + * @Then /^the content of file "([^"]*)" should be "([^"]*)" plus end-of-line$/ + * + * @param string $fileName + * @param string $content + * + * @return void + */ + public function contentOfFileShouldBePlusEndOfLine(string $fileName, string $content):void { + $this->theUserDownloadsTheFileUsingTheAPI($fileName); + $this->downloadedContentShouldBePlusEndOfLine($content); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileName + * @param string $user + * @param string $content + * + * @return void + */ + public function contentOfFileForUserShouldBe(string $fileName, string $user, string $content):void { + $user = $this->getActualUsername($user); + $this->downloadFileAsUserUsingPassword($user, $fileName); + $this->downloadedContentShouldBe($content); + } + + /** + * @Then /^the content of the following files for user "([^"]*)" should be "([^"]*)"$/ + * + * @param string $user + * @param string $content + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function contentOfFollowingFilesShouldBe(string $user, string $content, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $file) { + $this->contentOfFileForUserShouldBe($file["path"], $user, $content); + } + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" on server "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileName + * @param string $user + * @param string $server + * @param string $content + * + * @return void + */ + public function theContentOfFileForUserOnServerShouldBe( + string $fileName, + string $user, + string $server, + string $content + ):void { + $previousServer = $this->usingServer($server); + $this->contentOfFileForUserShouldBe($fileName, $user, $content); + $this->usingServer($previousServer); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" using password "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileName + * @param string $user + * @param string|null $password + * @param string $content + * + * @return void + */ + public function contentOfFileForUserUsingPasswordShouldBe( + string $fileName, + string $user, + ?string $password, + string $content + ):void { + $user = $this->getActualUsername($user); + $password = $this->getActualPassword($password); + $this->downloadFileAsUserUsingPassword($user, $fileName, $password); + $this->downloadedContentShouldBe($content); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" should be:$/ + * + * @param string $fileName + * @param string $user + * @param PyStringNode $content + * + * @return void + */ + public function contentOfFileForUserShouldBePyString( + string $fileName, + string $user, + PyStringNode $content + ):void { + $this->contentOfFileForUserShouldBe($fileName, $user, $content->getRaw()); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" using password "([^"]*)" should be:$/ + * + * @param string $fileName + * @param string $user + * @param string|null $password + * @param PyStringNode $content + * + * @return void + */ + public function contentOfFileForUserUsingPasswordShouldBePyString( + string $fileName, + string $user, + ?string $password, + PyStringNode $content + ):void { + $this->contentOfFileForUserUsingPasswordShouldBe( + $fileName, + $user, + $password, + $content->getRaw() + ); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" should be "([^"]*)" plus end-of-line$/ + * + * @param string $fileName + * @param string $user + * @param string $content + * + * @return void + */ + public function contentOfFileForUserShouldBePlusEndOfLine(string $fileName, string $user, string $content):void { + $this->contentOfFileForUserShouldBe( + $fileName, + $user, + "$content\n" + ); + } + + /** + * @Then the content of the following files for user :user should be the following plus end-of-line + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theContentOfTheFollowingFilesForUserShouldBeTheFollowingPlusEndOfLine(string $user, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["filename", "content"]); + $rows = $table->getHash(); + foreach ($rows as $row) { + $this->contentOfFileForUserShouldBePlusEndOfLine($row["filename"], $user, $row["content"]); + } + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" on server "([^"]*)" should be "([^"]*)" plus end-of-line$/ + * + * @param string $fileName + * @param string $user + * @param string $server + * @param string $content + * + * @return void + */ + public function theContentOfFileForUserOnServerShouldBePlusEndOfLine( + string $fileName, + string $user, + string $server, + string $content + ):void { + $previousServer = $this->usingServer($server); + $this->contentOfFileForUserShouldBePlusEndOfLine($fileName, $user, $content); + $this->usingServer($previousServer); + } + + /** + * @Then /^the content of file "([^"]*)" for user "([^"]*)" using password "([^"]*)" should be "([^"]*)" plus end-of-line$/ + * + * @param string $fileName + * @param string $user + * @param string|null $password + * @param string $content + * + * @return void + */ + public function contentOfFileForUserUsingPasswordShouldBePlusEndOfLine( + string $fileName, + string $user, + ?string $password, + string $content + ):void { + $user = $this->getActualUsername($user); + $this->contentOfFileForUserUsingPasswordShouldBe( + $fileName, + $user, + $password, + "$content\n" + ); + } + + /** + * @Then /^the downloaded content when downloading file "([^"]*)" with range "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileSource + * @param string $range + * @param string $content + * + * @return void + */ + public function downloadedContentWhenDownloadingWithRangeShouldBe( + string $fileSource, + string $range, + string $content + ):void { + $this->downloadFileWithRange($fileSource, $range); + $this->downloadedContentShouldBe($content); + } + + /** + * @Then /^the downloaded content when downloading file "([^"]*)" for user "([^"]*)" with range "([^"]*)" should be "([^"]*)"$/ + * + * @param string $fileSource + * @param string $user + * @param string $range + * @param string $content + * + * @return void + */ + public function downloadedContentWhenDownloadingForUserWithRangeShouldBe( + string $fileSource, + string $user, + string $range, + string $content + ):void { + $user = $this->getActualUsername($user); + $this->userDownloadsFileWithRange($user, $fileSource, $range); + $this->downloadedContentShouldBe($content); + } + + /** + * @When the user downloads the file :fileName using the WebDAV API + * + * @param string $fileName + * + * @return void + */ + public function theUserDownloadsTheFileUsingTheAPI(string $fileName):void { + $this->downloadFileAsUserUsingPassword($this->currentUser, $fileName); + } + + /** + * @When user :user downloads file :fileName using the WebDAV API + * + * @param string $user + * @param string $fileName + * + * @return void + */ + public function userDownloadsFileUsingTheAPI( + string $user, + string $fileName + ):void { + $this->downloadFileAsUserUsingPassword($user, $fileName); + } + + /** + * @When user :user using password :password downloads the file :fileName using the WebDAV API + * + * @param string $user + * @param string|null $password + * @param string $fileName + * + * @return void + */ + public function userUsingPasswordDownloadsTheFileUsingTheAPI( + string $user, + ?string $password, + string $fileName + ):void { + $this->downloadFileAsUserUsingPassword($user, $fileName, $password); + } + + /** + * @param string $user + * @param string $fileName + * @param string|null $password + * @param array|null $headers + * + * @return void + */ + public function downloadFileAsUserUsingPassword( + string $user, + string $fileName, + ?string $password = null, + ?array $headers = [] + ):void { + $user = $this->getActualUsername($user); + $password = $this->getActualPassword($password); + $this->response = $this->makeDavRequest( + $user, + 'GET', + $fileName, + $headers, + null, + "files", + null, + false, + $password + ); + } + + /** + * @When the public gets the size of the last shared public link using the WebDAV API + * + * @return void + * @throws Exception + */ + public function publicGetsSizeOfLastSharedPublicLinkUsingTheWebdavApi():void { + $tokenArray = $this->getLastPublicShareData()->data->token; + $token = (string)$tokenArray[0]; + $url = $this->getBaseUrl() . "/remote.php/dav/public-files/{$token}"; + $this->response = HttpRequestHelper::sendRequest( + $url, + $this->getStepLineRef(), + "PROPFIND", + null, + null, + null + ); + } + + /** + * @When user :user gets the size of file :resource using the WebDAV API + * + * @param string $user + * @param string $resource + * + * @return void + * @throws Exception + */ + public function userGetsSizeOfFileUsingTheWebdavApi(string $user, string $resource):void { + $user = $this->getActualUsername($user); + $password = $this->getPasswordForUser($user); + $this->response = WebDavHelper::propfind( + $this->getBaseUrl(), + $user, + $password, + $resource, + [], + $this->getStepLineRef(), + "0", + "files", + $this->getDavPathVersion() + ); + } + + /** + * @Then the size of the file should be :size + * + * @param string $size + * + * @return void + * @throws Exception + */ + public function theSizeOfTheFileShouldBe(string $size):void { + $responseXml = HttpRequestHelper::getResponseXml( + $this->response, + __METHOD__ + ); + $xmlPart = $responseXml->xpath("//d:prop/d:getcontentlength"); + $actualSize = (string) $xmlPart[0]; + Assert::assertEquals( + $size, + $actualSize, + __METHOD__ + . " Expected size of the file was '$size', but got '$actualSize' instead." + ); + } + + /** + * @Then the following headers should be set + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingHeadersShouldBeSet(TableNode $table):void { + $this->verifyTableNodeColumns( + $table, + ['header', 'value'] + ); + foreach ($table->getColumnsHash() as $header) { + $headerName = $header['header']; + $expectedHeaderValue = $header['value']; + $returnedHeader = $this->response->getHeader($headerName); + $expectedHeaderValue = $this->substituteInLineCodes($expectedHeaderValue); + + if (\is_array($returnedHeader)) { + if (empty($returnedHeader)) { + throw new Exception( + \sprintf( + "Missing expected header '%s'", + $headerName + ) + ); + } + $headerValue = $returnedHeader[0]; + } else { + $headerValue = $returnedHeader; + } + + Assert::assertEquals( + $expectedHeaderValue, + $headerValue, + __METHOD__ + . " Expected value for header '$headerName' was '$expectedHeaderValue', but got '$headerValue' instead." + ); + } + } + + /** + * @Then the downloaded content should start with :start + * + * @param string $start + * + * @return void + * @throws Exception + */ + public function downloadedContentShouldStartWith(string $start):void { + Assert::assertEquals( + 0, + \strpos($this->response->getBody()->getContents(), $start), + __METHOD__ + . " The downloaded content was expected to start with '$start', but actually started with '{$this->response->getBody()->getContents()}'" + ); + } + + /** + * @Then the oc job status values of last request for user :user should match these regular expressions + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function jobStatusValuesShouldMatchRegEx(string $user, TableNode $table):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($table, 2); + $headerArray = $this->response->getHeader("OC-JobStatus-Location"); + $url = $headerArray[0]; + $url = $this->getBaseUrlWithoutPath() . $url; + $response = HttpRequestHelper::get( + $url, + $this->getStepLineRef(), + $user, + $this->getPasswordForUser($user) + ); + $contents = $response->getBody()->getContents(); + $result = \json_decode($contents, true); + PHPUnit\Framework\Assert::assertNotNull($result, "'$contents' is not valid JSON"); + foreach ($table->getTable() as $row) { + $expectedKey = $row[0]; + Assert::assertArrayHasKey( + $expectedKey, + $result, + "response does not have expected key '$expectedKey'" + ); + $expectedValue = $this->substituteInLineCodes( + $row[1], + $user, + ['preg_quote' => ['/']] + ); + Assert::assertNotFalse( + (bool) \preg_match($expectedValue, (string)$result[$expectedKey]), + "'$expectedValue' does not match '$result[$expectedKey]'" + ); + } + } + + /** + * @Then /^as "([^"]*)" (file|folder|entry) "([^"]*)" should not exist$/ + * + * @param string $user + * @param string $entry + * @param string|null $path + * @param string $type + * + * @return ResponseInterface + * @throws Exception + */ + public function asFileOrFolderShouldNotExist( + string $user, + string $entry = "file", + ?string $path = null, + string $type = "files" + ):ResponseInterface { + $user = $this->getActualUsername($user); + $path = $this->substituteInLineCodes($path); + $response = $this->listFolder( + $user, + $path, + '0', + null, + $type + ); + $statusCode = $response->getStatusCode(); + if ($statusCode < 401 || $statusCode > 404) { + try { + $this->responseXmlObject = HttpRequestHelper::getResponseXml( + $response, + __METHOD__ + ); + } catch (Exception $e) { + Assert::fail( + "$entry '$path' should not exist. But API returned $statusCode without XML in the body" + ); + } + Assert::assertTrue( + $this->isEtagValid(), + "$entry '$path' should not exist. But API returned $statusCode without an etag in the body" + ); + $isCollection = $this->getResponseXmlObject()->xpath("//d:prop/d:resourcetype/d:collection"); + if (\count($isCollection) === 0) { + $actualResourceType = "file"; + } else { + $actualResourceType = "folder"; + } + Assert::fail( + "$entry '$path' should not exist. But it does exist and is a $actualResourceType" + ); + } + return $response; + } + + /** + * @Then /^as "([^"]*)" the following (files|folders) should not exist$/ + * + * @param string $user + * @param string $entry + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function followingFilesShouldNotExist( + string $user, + string $entry, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + $entry = \rtrim($entry, "s"); + + foreach ($paths as $file) { + $this->asFileOrFolderShouldNotExist($user, $entry, $file["path"]); + } + } + + /** + * @Then /^as "([^"]*)" (file|folder|entry) "([^"]*)" should exist$/ + * + * @param string $user + * @param string $entry + * @param string $path + * @param string $type + * + * @return void + * @throws Exception + */ + public function asFileOrFolderShouldExist( + string $user, + string $entry, + string $path, + string $type = "files" + ):void { + $user = $this->getActualUsername($user); + $path = $this->substituteInLineCodes($path); + $this->responseXmlObject = $this->listFolderAndReturnResponseXml( + $user, + $path, + '0', + null, + $type + ); + Assert::assertTrue( + $this->isEtagValid(), + "$entry '$path' expected to exist for user $user but not found" + ); + $isCollection = $this->getResponseXmlObject()->xpath("//d:prop/d:resourcetype/d:collection"); + if ($entry === "folder") { + Assert::assertEquals(\count($isCollection), 1, "Unexpectedly, `$path` is not a folder"); + } elseif ($entry === "file") { + Assert::assertEquals(\count($isCollection), 0, "Unexpectedly, `$path` is not a file"); + } + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @Then /^as "([^"]*)" the following (files|folders) should exist$/ + * + * @param string $user + * @param string $entry + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function followingFilesOrFoldersShouldExist( + string $user, + string $entry, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + $entry = \rtrim($entry, "s"); + + foreach ($paths as $file) { + $this->asFileOrFolderShouldExist($user, $entry, $file["path"]); + } + } + + /** + * + * @param string $user + * @param string $entry + * @param string $path + * @param string $type + * + * @return bool + */ + public function fileOrFolderExists( + string $user, + string $entry, + string $path, + string $type = "files" + ):bool { + try { + $this->asFileOrFolderShouldExist($user, $entry, $path, $type); + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * @Then /^as "([^"]*)" exactly one of these (files|folders|entries) should exist$/ + * + * @param string $user + * @param string $entries + * @param TableNode $table of file, folder or entry paths + * + * @return void + * @throws Exception + */ + public function asExactlyOneOfTheseFilesOrFoldersShouldExist(string $user, string $entries, TableNode $table):void { + $numEntriesThatExist = 0; + foreach ($table->getTable() as $row) { + $path = $this->substituteInLineCodes($row[0]); + $this->responseXmlObject = $this->listFolderAndReturnResponseXml( + $user, + $path, + '0' + ); + if ($this->isEtagValid()) { + $numEntriesThatExist = $numEntriesThatExist + 1; + } + } + Assert::assertEquals( + 1, + $numEntriesThatExist, + "exactly one of these $entries should exist but found $numEntriesThatExist $entries" + ); + } + + /** + * + * @param string $user + * @param string $path + * @param string $folderDepth requires 1 to see elements without children + * @param array|null $properties + * @param string $type + * + * @return ResponseInterface + * @throws Exception + */ + public function listFolder( + string $user, + string $path, + string $folderDepth, + ?array $properties = null, + string $type = "files" + ):ResponseInterface { + if ($this->customDavPath !== null) { + $path = $this->customDavPath . $path; + } + + return WebDavHelper::listFolder( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getPasswordForUser($user), + $path, + $folderDepth, + $this->getStepLineRef(), + $properties, + $type, + $this->getDavPathVersion() + ); + } + + /** + * + * @param string $user + * @param string $path + * @param string $folderDepth requires 1 to see elements without children + * @param array|null $properties + * @param string $type + * + * @return SimpleXMLElement + * @throws Exception + */ + public function listFolderAndReturnResponseXml( + string $user, + string $path, + string $folderDepth, + ?array $properties = null, + string $type = "files" + ):SimpleXMLElement { + return HttpRequestHelper::getResponseXml( + $this->listFolder( + $user, + $path, + $folderDepth, + $properties, + $type + ), + __METHOD__ + ); + } + + /** + * @Then /^user "([^"]*)" should (not|)\s?see the following elements$/ + * + * @param string $user + * @param string $shouldOrNot + * @param TableNode $elements + * + * @return void + * @throws InvalidArgumentException|Exception + * + */ + public function userShouldSeeTheElements(string $user, string $shouldOrNot, TableNode $elements):void { + $should = ($shouldOrNot !== "not"); + $this->checkElementList($user, $elements, $should); + } + + /** + * @Then /^user "([^"]*)" should not see the following elements if the upper and lower case username are different/ + * + * @param string $user + * @param TableNode $elements + * + * @return void + * @throws InvalidArgumentException|Exception + * + */ + public function userShouldNotSeeTheElementsIfUpperAndLowerCaseUsernameDifferent(string $user, TableNode $elements):void { + $effectiveUser = $this->getActualUsername($user); + if (\strtoupper($effectiveUser) === \strtolower($effectiveUser)) { + $expectedToBeListed = true; + } else { + $expectedToBeListed = false; + } + $this->checkElementList($user, $elements, $expectedToBeListed); + } + + /** + * asserts that the user can or cannot see a list of files/folders by propfind + * + * @param string $user + * @param TableNode $elements + * @param boolean $expectedToBeListed + * + * @return void + * @throws InvalidArgumentException + * @throws Exception + * + */ + public function checkElementList( + string $user, + TableNode $elements, + bool $expectedToBeListed = true + ):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($elements, 1); + $elementRows = $elements->getRows(); + $elementsSimplified = $this->simplifyArray($elementRows); + if ($this->davPropfindDepthInfinityIsEnabled()) { + // get a full "infinite" list of the user's root folder in one request + // and process that to check the elements (resources) + $responseXmlObject = $this->listFolderAndReturnResponseXml( + $user, + "/", + "infinity" + ); + foreach ($elementsSimplified as $expectedElement) { + // Allow the table of expected elements to have entries that do + // not have to specify the "implied" leading slash, or have multiple + // leading slashes, to make scenario outlines more flexible + $expectedElement = $this->encodePath($expectedElement); + $expectedElement = "/" . \ltrim($expectedElement, "/"); + $webdavPath = "/" . $this->getFullDavFilesPath($user) . $expectedElement; + $element = $responseXmlObject->xpath( + "//d:response/d:href[text() = \"$webdavPath\"]" + ); + if ($expectedToBeListed + && (!isset($element[0]) || urldecode($element[0]->__toString()) !== urldecode($webdavPath)) + ) { + Assert::fail( + "$webdavPath is not in propfind answer but should be" + ); + } elseif (!$expectedToBeListed && isset($element[0]) + ) { + Assert::fail( + "$webdavPath is in propfind answer but should not be" + ); + } + } + } else { + // do a PROPFIND for each element + foreach ($elementsSimplified as $elementToRequest) { + // Allow the table of expected elements to have entries that do + // not have to specify the "implied" leading slash, or have multiple + // leading slashes, to make scenario outlines more flexible + $elementToRequest = "/" . \ltrim($elementToRequest, "/"); + // Note: in the request we ask to do a PROPFIND on a resource like: + // /some-folder with spaces/sub-folder + // but the response has encoded values for the special characters like: + // /some-folder%20with%20spaces/sub-folder + // So we need both $elementToRequest and $expectedElement + $expectedElement = $this->encodePath($elementToRequest); + $responseXmlObject = $this->listFolderAndReturnResponseXml( + $user, + $elementToRequest, + "1" + ); + $webdavPath = "/" . $this->getFullDavFilesPath($user) . $expectedElement; + $element = $responseXmlObject->xpath( + "//d:response/d:href[text() = \"$webdavPath\"]" + ); + if ($expectedToBeListed + && (!isset($element[0]) || urldecode($element[0]->__toString()) !== urldecode($webdavPath)) + ) { + Assert::fail( + "$webdavPath is not in propfind answer but should be" + ); + } elseif (!$expectedToBeListed && isset($element[0]) + ) { + Assert::fail( + "$webdavPath is in propfind answer but should not be" + ); + } + } + } + } + + /** + * @When user :user uploads file :source to :destination using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + */ + public function userUploadsAFileTo(string $user, string $source, string $destination):void { + $user = $this->getActualUsername($user); + $file = \fopen($this->acceptanceTestsDirLocation() . $source, 'r'); + $this->pauseUploadDelete(); + $this->response = $this->makeDavRequest( + $user, + "PUT", + $destination, + [], + $file + ); + $this->lastUploadDeleteTime = \time(); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + $this->pushToLastHttpStatusCodesArray( + (string) $this->getResponse()->getStatusCode() + ); + } + + /** + * @Given user :user has uploaded file :source to :destination + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + */ + public function userHasUploadedAFileTo(string $user, string $source, string $destination):void { + $this->userUploadsAFileTo($user, $source, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$source' to '$destination' for user '$user'" + ); + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @When the user uploads file :source to :destination using the WebDAV API + * + * @param string $source + * @param string $destination + * + * @return void + */ + public function theUserUploadsAFileTo(string $source, string $destination):void { + $this->userUploadsAFileTo($this->currentUser, $source, $destination); + } + + /** + * @Given the user has uploaded file :source to :destination + * + * @param string $source + * @param string $destination + * + * @return void + */ + public function theUserHasUploadedFileTo(string $source, string $destination):void { + $this->theUserUploadsAFileTo($source, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$source' to '$destination'" + ); + } + + /** + * @When /^user "([^"]*)" on "(LOCAL|REMOTE)" uploads file "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $server + * @param string $source + * @param string $destination + * + * @return void + */ + public function userOnUploadsAFileTo(string $user, string $server, string $source, string $destination):void { + $previousServer = $this->usingServer($server); + $this->userUploadsAFileTo($user, $source, $destination); + $this->usingServer($previousServer); + } + + /** + * @Given /^user "([^"]*)" on "(LOCAL|REMOTE)" has uploaded file "([^"]*)" to "([^"]*)"$/ + * + * @param string $user + * @param string $server + * @param string $source + * @param string $destination + * + * @return void + */ + public function userOnHasUploadedAFileTo(string $user, string $server, string $source, string $destination):void { + $this->userOnUploadsAFileTo($user, $server, $source, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$source' to '$destination' for user '$user' on server '$server'" + ); + } + + /** + * Upload file as a user with different headers + * + * @param string $user + * @param string $source + * @param string $destination + * @param array|null $headers + * @param int|null $noOfChunks Only use for chunked upload when $this->chunkingToUse is not null + * + * @return void + * @throws Exception + */ + public function uploadFileWithHeaders( + string $user, + string $source, + string $destination, + ?array $headers = [], + ?int $noOfChunks = 0 + ):void { + $chunkingVersion = $this->chunkingToUse; + if ($noOfChunks <= 0) { + $chunkingVersion = null; + } + try { + $this->responseXml = []; + $this->pauseUploadDelete(); + $this->response = UploadHelper::upload( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getUserPassword($user), + $source, + $destination, + $this->getStepLineRef(), + $headers, + $this->getDavPathVersion(), + $chunkingVersion, + $noOfChunks + ); + $this->lastUploadDeleteTime = \time(); + } catch (BadResponseException $e) { + // 4xx and 5xx responses cause an exception + $this->response = $e->getResponse(); + } + } + + /** + * @When /^user "([^"]*)" uploads file "([^"]*)" to "([^"]*)" in (\d+) chunks (?:with (new|old|v1|v2) chunking and)?\s?using the WebDAV API$/ + * @When user :user uploads file :source to :destination with chunks using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * @param int $noOfChunks + * @param string|null $chunkingVersion old|v1|new|v2 null for autodetect + * @param bool $async use asynchronous move at the end or not + * @param array|null $headers + * + * @return void + * @throws Exception + */ + public function userUploadsAFileToWithChunks( + string $user, + string $source, + string $destination, + int $noOfChunks = 2, + ?string $chunkingVersion = null, + bool $async = false, + ?array $headers = [] + ):void { + $user = $this->getActualUsername($user); + Assert::assertGreaterThan( + 0, + $noOfChunks, + "What does it mean to have $noOfChunks chunks?" + ); + //use the chunking version that works with the set DAV version + if ($chunkingVersion === null) { + if ($this->usingOldDavPath || $this->usingSpacesDavPath) { + $chunkingVersion = "v1"; + } else { + $chunkingVersion = "v2"; + } + } + $this->useSpecificChunking($chunkingVersion); + Assert::assertTrue( + WebDavHelper::isValidDavChunkingCombination( + $this->getDavPathVersion(), + $this->chunkingToUse + ), + "invalid chunking/webdav version combination" + ); + + if ($async === true) { + $headers['OC-LazyOps'] = 'true'; + } + $this->uploadFileWithHeaders( + $user, + $this->acceptanceTestsDirLocation() . $source, + $destination, + $headers, + $noOfChunks + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @When /^user "([^"]*)" uploads file "([^"]*)" asynchronously to "([^"]*)" in (\d+) chunks (?:with (new|old|v1|v2) chunking and)?\s?using the WebDAV API$/ + * + * @param string $user + * @param string $source + * @param string $destination + * @param int $noOfChunks + * @param string|null $chunkingVersion old|v1|new|v2 null for autodetect + * + * @return void + * @throws Exception + */ + public function userUploadsAFileAsyncToWithChunks( + string $user, + string $source, + string $destination, + int $noOfChunks = 2, + ?string $chunkingVersion = null + ):void { + $user = $this->getActualUsername($user); + $this->userUploadsAFileToWithChunks( + $user, + $source, + $destination, + $noOfChunks, + $chunkingVersion, + true + ); + } + + /** + * sets the chunking version from human readable format + * + * @param string $version (no|v1|v2|new|old) + * + * @return void + */ + public function useSpecificChunking(string $version):void { + if ($version === "v1" || $version === "old") { + $this->chunkingToUse = 1; + } elseif ($version === "v2" || $version === "new") { + $this->chunkingToUse = 2; + } elseif ($version === "no") { + $this->chunkingToUse = null; + } else { + throw new InvalidArgumentException( + "cannot set chunking version to $version" + ); + } + } + + /** + * Uploading with old/new DAV and chunked/non-chunked. + * + * @When user :user uploads file :source to filenames based on :destination with all mechanisms using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userUploadsAFileToWithAllMechanisms( + string $user, + string $source, + string $destination + ):void { + $user = $this->getActualUsername($user); + $this->uploadResponses = UploadHelper::uploadWithAllMechanisms( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getUserPassword($user), + $this->acceptanceTestsDirLocation() . $source, + $destination, + $this->getStepLineRef() + ); + } + + /** + * Uploading with old/new DAV and chunked/non-chunked. + * Except do not do the new-DAV-new-chunking combination. That is not being + * supported on all implementations. + * + * @When user :user uploads file :source to filenames based on :destination with all mechanisms except new chunking using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userUploadsAFileToWithAllMechanismsExceptNewChunking( + string $user, + string $source, + string $destination + ):void { + $user = $this->getActualUsername($user); + $this->uploadResponses = UploadHelper::uploadWithAllMechanisms( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getUserPassword($user), + $this->acceptanceTestsDirLocation() . $source, + $destination, + $this->getStepLineRef(), + false, + 'new' + ); + + $this->pushToLastStatusCodesArrays(); + } + + /** + * Overwriting with old/new DAV and chunked/non-chunked. + * + * @When user :user overwrites from file :source to file :destination with all mechanisms using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userOverwritesAFileToWithAllMechanisms( + string $user, + string $source, + string $destination + ):void { + $user = $this->getActualUsername($user); + $this->uploadResponses = UploadHelper::uploadWithAllMechanisms( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getUserPassword($user), + $this->acceptanceTestsDirLocation() . $source, + $destination, + $this->getStepLineRef(), + true + ); + } + + /** + * Overwriting with old/new DAV and chunked/non-chunked. + * Except do not do the new-DAV-new-chunking combination. That is not being + * supported on all implementations. + * + * @When user :user overwrites from file :source to file :destination with all mechanisms except new chunking using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userOverwritesAFileToWithAllMechanismsExceptNewChunking( + string $user, + string $source, + string $destination + ):void { + $user = $this->getActualUsername($user); + $this->uploadResponses = UploadHelper::uploadWithAllMechanisms( + $this->getBaseUrl(), + $this->getActualUsername($user), + $this->getUserPassword($user), + $this->acceptanceTestsDirLocation() . $source, + $destination, + $this->getStepLineRef(), + true, + 'new' + ); + } + + /** + * @Then /^the HTTP status code of all upload responses should be "([^"]*)"$/ + * + * @param int $statusCode + * + * @return void + */ + public function theHTTPStatusCodeOfAllUploadResponsesShouldBe(int $statusCode):void { + foreach ($this->uploadResponses as $response) { + Assert::assertEquals( + $statusCode, + $response->getStatusCode(), + 'Response did not return expected status code' + ); + } + } + + /** + * @Then the HTTP status code of responses on all endpoints should be :statusCode + * + * @param int $statusCode + * + * @return void + * @throws Exception + */ + public function theHTTPStatusCodeOfResponsesOnAllEndpointsShouldBe(int $statusCode):void { + $duplicateRemovedStatusCodes = \array_unique($this->lastHttpStatusCodesArray); + if (\count($duplicateRemovedStatusCodes) === 1) { + Assert::assertSame( + \intval($statusCode), + \intval($duplicateRemovedStatusCodes[0]), + 'Responses did not return expected http status code' + ); + $this->emptyLastHTTPStatusCodesArray(); + } else { + throw new Exception( + 'Expected same but found different http status codes of last requested responses.' . + 'Found status codes: ' . \implode(',', $this->lastHttpStatusCodesArray) + ); + } + } + + /** + * @Then the HTTP status code of responses on each endpoint should be :statusCode respectively + * + * @param string $statusCodes + * + * @return void + * @throws Exception + */ + public function theHTTPStatusCodeOfResponsesOnEachEndpointShouldBe(string $statusCodes):void { + $statusCodes = \explode(',', $statusCodes); + $count = \count($statusCodes); + if ($count === \count($this->lastHttpStatusCodesArray)) { + for ($i = 0; $i < $count; $i++) { + Assert::assertSame( + (int)\trim($statusCodes[$i]), + (int)$this->lastHttpStatusCodesArray[$i], + 'Responses did not return expected HTTP status code' + ); + } + $this->emptyLastHTTPStatusCodesArray(); + } else { + throw new Exception( + 'Expected HTTP status codes: "' . \implode(',', $statusCodes) . + '". Found HTTP status codes: "' . \implode(',', $this->lastHttpStatusCodesArray) . '"' + ); + } + } + + /** + * @Then the OCS status code of responses on each endpoint should be :statusCode respectively + * + * @param string $statusCodes + * + * @return void + * @throws Exception + */ + public function theOCStatusCodeOfResponsesOnEachEndpointShouldBe(string $statusCodes):void { + $statusCodes = \explode(',', $statusCodes); + $count = \count($statusCodes); + if ($count === \count($this->lastOCSStatusCodesArray)) { + for ($i = 0; $i < $count; $i++) { + Assert::assertSame( + (int)\trim($statusCodes[$i]), + (int)$this->lastOCSStatusCodesArray[$i], + 'Responses did not return expected OCS status code' + ); + } + } else { + throw new Exception( + 'Expected OCS status codes: "' . \implode(',', $statusCodes) . + '". Found OCS status codes: "' . \implode(',', $this->lastOCSStatusCodesArray) . '"' + ); + } + } + + /** + * @Then the HTTP status code of responses on all endpoints should be :statusCode1 or :statusCode2 + * + * @param string $statusCode1 + * @param string $statusCode2 + * + * @return void + * @throws Exception + */ + public function theHTTPStatusCodeOfResponsesOnAllEndpointsShouldBeOr(string $statusCode1, string $statusCode2):void { + $duplicateRemovedStatusCodes = \array_unique($this->lastHttpStatusCodesArray); + foreach ($duplicateRemovedStatusCodes as $status) { + $status = (string)$status; + if (($status != $statusCode1) && ($status != $statusCode2)) { + Assert::fail("Unexpected status code received " . $status); + } + } + } + + /** + * @Then the OCS status code of responses on all endpoints should be :statusCode + * + * @param string $statusCode + * + * @return void + * @throws Exception + */ + public function theOCSStatusCodeOfResponsesOnAllEndpointsShouldBe(string $statusCode):void { + $duplicateRemovedStatusCodes = \array_unique($this->lastOCSStatusCodesArray); + if (\count($duplicateRemovedStatusCodes) === 1) { + Assert::assertSame( + \intval($statusCode), + \intval($duplicateRemovedStatusCodes[0]), + 'Responses did not return expected ocs status code' + ); + $this->emptyLastOCSStatusCodesArray(); + } else { + throw new Exception( + 'Expected same but found different ocs status codes of last requested responses.' . + 'Found status codes: ' . \implode(',', $this->lastOCSStatusCodesArray) + ); + } + } + + /** + * @Then /^the HTTP reason phrase of all upload responses should be "([^"]*)"$/ + * + * @param string $reasonPhrase + * + * @return void + */ + public function theHTTPReasonPhraseOfAllUploadResponsesShouldBe(string $reasonPhrase):void { + foreach ($this->uploadResponses as $response) { + Assert::assertEquals( + $reasonPhrase, + $response->getReasonPhrase(), + 'Response did not return expected reason phrase' + ); + } + } + + /** + * @Then user :user should be able to upload file :source to :destination + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userShouldBeAbleToUploadFileTo(string $user, string $source, string $destination):void { + $user = $this->getActualUsername($user); + $this->userUploadsAFileTo($user, $source, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$destination'" + ); + $this->asFileOrFolderShouldExist($user, "file", $destination); + } + + /** + * @Then the following users should be able to upload file :source to :destination + * + * @param string $source + * @param string $destination + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function usersShouldBeAbleToUploadFileTo( + string $source, + string $destination, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["username"]); + $usernames = $table->getHash(); + foreach ($usernames as $username) { + $actualUser = $this->getActualUsername($username["username"]); + $this->userUploadsAFileTo($actualUser, $source, $destination); + $this->asFileOrFolderShouldExist($actualUser, "file", $destination); + } + } + + /** + * @Then user :user should not be able to upload file :source to :destination + * + * @param string $user + * @param string $source + * @param string $destination + * + * @return void + * @throws Exception + */ + public function theUserShouldNotBeAbleToUploadFileTo(string $user, string $source, string $destination):void { + $fileAlreadyExists = $this->fileOrFolderExists($user, "file", $destination); + if ($fileAlreadyExists) { + $this->downloadFileAsUserUsingPassword($user, $destination); + $initialContent = (string) $this->response->getBody(); + } + $this->userUploadsAFileTo($user, $source, $destination); + $this->theHTTPStatusCodeShouldBe(["403", "423"]); + if ($fileAlreadyExists) { + $this->downloadFileAsUserUsingPassword($user, $destination); + $currentContent = (string) $this->response->getBody(); + Assert::assertSame( + $initialContent, + $currentContent, + __METHOD__ . " user $user was unexpectedly able to upload $source to $destination - the content has changed:" + ); + } else { + $this->asFileOrFolderShouldNotExist($user, "file", $destination); + } + } + + /** + * @Then /^the HTTP status code of all upload responses should be between "(\d+)" and "(\d+)"$/ + * + * @param int $minStatusCode + * @param int $maxStatusCode + * + * @return void + */ + public function theHTTPStatusCodeOfAllUploadResponsesShouldBeBetween( + int $minStatusCode, + int $maxStatusCode + ):void { + foreach ($this->uploadResponses as $response) { + Assert::assertGreaterThanOrEqual( + $minStatusCode, + $response->getStatusCode(), + 'Response did not return expected status code' + ); + Assert::assertLessThanOrEqual( + $maxStatusCode, + $response->getStatusCode(), + 'Response did not return expected status code' + ); + } + } + + /** + * Check that all the files uploaded with old/new DAV and chunked/non-chunked exist. + * + * @Then /^as "([^"]*)" the files uploaded to "([^"]*)" with all mechanisms should (not|)\s?exist$/ + * + * @param string $user + * @param string $destination + * @param string $shouldOrNot + * @param string|null $exceptChunkingType empty string or "old" or "new" + * + * @return void + * @throws Exception + */ + public function filesUploadedToWithAllMechanismsShouldExist( + string $user, + string $destination, + string $shouldOrNot, + ?string $exceptChunkingType = '' + ):void { + switch ($exceptChunkingType) { + case 'old': + $exceptChunkingSuffix = 'olddav-oldchunking'; + break; + case 'new': + $exceptChunkingSuffix = 'newdav-newchunking'; + break; + default: + $exceptChunkingSuffix = ''; + break; + } + + if ($shouldOrNot !== "not") { + foreach (['old', 'new'] as $davVersion) { + foreach (["{$davVersion}dav-regular", "{$davVersion}dav-{$davVersion}chunking"] as $suffix) { + if ($suffix !== $exceptChunkingSuffix) { + $this->asFileOrFolderShouldExist( + $user, + 'file', + "$destination-$suffix" + ); + } + } + } + } else { + foreach (['old', 'new'] as $davVersion) { + foreach (["{$davVersion}dav-regular", "{$davVersion}dav-{$davVersion}chunking"] as $suffix) { + if ($suffix !== $exceptChunkingSuffix) { + $this->asFileOrFolderShouldNotExist( + $user, + 'file', + "$destination-$suffix" + ); + } + } + } + } + } + + /** + * Check that all the files uploaded with old/new DAV and chunked/non-chunked exist. + * Except do not check the new-DAV-new-chunking combination. That is not being + * supported on all implementations. + * + * @Then /^as "([^"]*)" the files uploaded to "([^"]*)" with all mechanisms except new chunking should (not|)\s?exist$/ + * + * @param string $user + * @param string $destination + * @param string $shouldOrNot + * + * @return void + * @throws Exception + */ + public function filesUploadedToWithAllMechanismsExceptNewChunkingShouldExist( + string $user, + string $destination, + string $shouldOrNot + ):void { + $this->filesUploadedToWithAllMechanismsShouldExist( + $user, + $destination, + $shouldOrNot, + 'new' + ); + } + + /** + * @Then /^as user "([^"]*)" on server "([^"]*)" the files uploaded to "([^"]*)" with all mechanisms should (not|)\s?exist$/ + * + * @param string $user + * @param string $server + * @param string $destination + * @param string $shouldOrNot + * + * @return void + * @throws Exception + */ + public function asUserOnServerTheFilesUploadedToWithAllMechanismsShouldExit( + string $user, + string $server, + string $destination, + string $shouldOrNot + ):void { + $previousServer = $this->usingServer($server); + $this->filesUploadedToWithAllMechanismsShouldExist($user, $destination, $shouldOrNot); + $this->usingServer($previousServer); + } + + /** + * @Given user :user has uploaded file :destination of size :bytes bytes + * + * @param string $user + * @param string $destination + * @param string $bytes + * + * @return void + * @throws Exception + */ + public function userHasUploadedFileToOfSizeBytes(string $user, string $destination, string $bytes):void { + $user = $this->getActualUsername($user); + $this->userUploadsAFileToOfSizeBytes($user, $destination, $bytes); + $expectedElements = new TableNode([["$destination"]]); + $this->checkElementList($user, $expectedElements); + } + + /** + * @When user :user uploads file :destination of size :bytes bytes + * + * @param string $user + * @param string $destination + * @param string $bytes + * + * @return void + */ + public function userUploadsAFileToOfSizeBytes(string $user, string $destination, string $bytes):void { + $this->userUploadsAFileToEndingWithOfSizeBytes($user, $destination, 'a', $bytes); + } + + /** + * @Given user :user has uploaded file :destination ending with :text of size :bytes bytes + * + * @param string $user + * @param string $destination + * @param string $text + * @param string $bytes + * + * @return void + * @throws Exception + */ + public function userHasUploadedFileToEndingWithOfSizeBytes(string $user, string $destination, string $text, string $bytes):void { + $this->userUploadsAFileToEndingWithOfSizeBytes($user, $destination, $text, $bytes); + $expectedElements = new TableNode([["$destination"]]); + $this->checkElementList($user, $expectedElements); + } + + /** + * @When user :user uploads file :destination ending with :text of size :bytes bytes + * + * @param string $user + * @param string $destination + * @param string $text + * @param string $bytes + * + * @return void + */ + public function userUploadsAFileToEndingWithOfSizeBytes(string $user, string $destination, string $text, string $bytes):void { + $filename = "filespecificSize.txt"; + $this->createLocalFileOfSpecificSize($filename, $bytes, $text); + Assert::assertFileExists($this->workStorageDirLocation() . $filename); + $this->userUploadsAFileTo( + $user, + $this->temporaryStorageSubfolderName() . "/$filename", + $destination + ); + $this->removeFile($this->workStorageDirLocation(), $filename); + } + + /** + * @When user :user uploads to these filenames with content :content using the webDAV API then the results should be as listed + * + * @param string $user + * @param string $content + * @param TableNode $table + * + * @return void + * @throws Exception + * @throws GuzzleException + */ + public function userUploadsFilesWithContentTo( + string $user, + string $content, + TableNode $table + ):void { + $user = $this->getActualUsername($user); + foreach ($table->getHash() as $row) { + $this->userUploadsAFileWithContentTo( + $user, + $content, + $row['filename'] + ); + $this->theHTTPStatusCodeShouldBe( + $row['http-code'], + "HTTP status code was not the expected value " . $row['http-code'] . " while trying to upload " . $row['filename'] + ); + if ($row['exists'] === "yes") { + $this->asFileOrFolderShouldExist($user, "file", $row['filename']); + } else { + $this->asFileOrFolderShouldNotExist($user, "file", $row['filename']); + } + } + } + + /** + * @param string $user + * @param string|null $content + * @param string $destination + * + * @return string[] + * @throws JsonException + * @throws GuzzleException + */ + public function uploadFileWithContent( + string $user, + ?string $content, + string $destination + ): array { + $user = $this->getActualUsername($user); + $this->pauseUploadDelete(); + $this->response = $this->makeDavRequest( + $user, + "PUT", + $destination, + [], + $content + ); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + $this->lastUploadDeleteTime = \time(); + return $this->response->getHeader('oc-fileid'); + } + + /** + * @When the administrator uploads file with content :content to :destination using the WebDAV API + * + * @param string|null $content + * @param string $destination + * + * @return string[] + */ + public function adminUploadsAFileWithContentTo( + ?string $content, + string $destination + ):array { + return $this->uploadFileWithContent($this->getAdminUsername(), $content, $destination); + } + + /** + * @Given the administrator has uploaded file with content :content to :destination + * + * @param string|null $content + * @param string $destination + * + * @return string[] + */ + public function adminHasUploadedAFileWithContentTo( + ?string $content, + string $destination + ):array { + $fileId = $this->uploadFileWithContent($this->getAdminUsername(), $content, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$destination'" + ); + return $fileId; + } + + /** + * @When user :user uploads file with content :content to :destination using the WebDAV API + * + * @param string $user + * @param string|null $content + * @param string $destination + * + * @return void + * @throws GuzzleException + * @throws JsonException + */ + public function userUploadsAFileWithContentTo( + string $user, + ?string $content, + string $destination + ):void { + $this->uploadFileWithContent($user, $content, $destination); + $this->pushToLastHttpStatusCodesArray(); + } + + /** + * @When /^user "([^"]*)" uploads the following files with content "([^"]*)"$/ + * + * @param string $user + * @param string|null $content + * @param TableNode $table + * + * @return void + * @throws Exception|GuzzleException + */ + public function userUploadsFollowingFilesWithContentTo( + string $user, + ?string $content, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $destination) { + $this->uploadFileWithContent($user, $content, $destination["path"]); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :user uploads file :source to :destination with mtime :mtime using the WebDAV API + * @Given user :user has uploaded file :source to :destination with mtime :mtime using the WebDAV API + * + * @param string $user + * @param string $source + * @param string $destination + * @param string $mtime Time in human readable format is taken as input which is converted into milliseconds that is used by API + * + * @return void + * @throws Exception + */ + public function userUploadsFileToWithMtimeUsingTheWebdavApi( + string $user, + string $source, + string $destination, + string $mtime + ):void { + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + $user = $this->getActualUsername($user); + $this->response = UploadHelper::upload( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + $this->acceptanceTestsDirLocation() . $source, + $destination, + $this->getStepLineRef(), + ["X-OC-Mtime" => $mtime], + $this->getDavPathVersion() + ); + } + + /** + * @Given user :user has uploaded file :filename with content :content and mtime of :days days ago using the WebDAV API + * + * @param string $user + * @param string $filename + * @param string $content + * @param string $days In days, e.g. "7" + * + * @return void + * @throws Exception + */ + public function userUploadsFileWithContentAndWithMtimeOfDaysAgoUsingWebdavApi( + string $user, + string $filename, + string $content, + string $days + ): void { + $today = new DateTime(); + $interval = new DateInterval('P' . $days . 'D'); + $mtime = $today->sub($interval)->format('U'); + + $user = $this->getActualUsername($user); + + $this->response = WebDavHelper::makeDavRequest( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + "PUT", + $filename, + ["X-OC-Mtime" => $mtime], + $this->getStepLineRef(), + $content + ); + } + + /** + * @Then as :user the mtime of the file :resource should be :mtime + * + * @param string $user + * @param string $resource + * @param string $mtime + * + * @return void + * @throws Exception + */ + public function theMtimeOfTheFileShouldBe( + string $user, + string $resource, + string $mtime + ):void { + $user = $this->getActualUsername($user); + $password = $this->getPasswordForUser($user); + $baseUrl = $this->getBaseUrl(); + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + Assert::assertEquals( + $mtime, + WebDavHelper::getMtimeOfResource( + $user, + $password, + $baseUrl, + $resource, + $this->getStepLineRef(), + $this->getDavPathVersion() + ) + ); + } + + /** + * @Then as :user the mtime of the file :resource should not be :mtime + * + * @param string $user + * @param string $resource + * @param string $mtime + * + * @return void + * @throws Exception + */ + public function theMtimeOfTheFileShouldNotBe( + string $user, + string $resource, + string $mtime + ):void { + $user = $this->getActualUsername($user); + $password = $this->getPasswordForUser($user); + $baseUrl = $this->getBaseUrl(); + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + Assert::assertNotEquals( + $mtime, + WebDavHelper::getMtimeOfResource( + $user, + $password, + $baseUrl, + $resource, + $this->getStepLineRef(), + $this->getDavPathVersion() + ) + ); + } + + /** + * @Given user :user has uploaded file with content :content to :destination + * + * @param string $user + * @param string|null $content + * @param string $destination + * + * @return string[] + */ + public function userHasUploadedAFileWithContentTo( + string $user, + ?string $content, + string $destination + ):array { + $user = $this->getActualUsername($user); + $fileId = $this->uploadFileWithContent($user, $content, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$destination' for user '$user'" + ); + $this->emptyLastHTTPStatusCodesArray(); + return $fileId; + } + + /** + * @Given /^user "([^"]*)" has uploaded the following files with content "([^"]*)"$/ + * + * @param string $user + * @param string|null $content + * @param TableNode $table + * + * @return array + * @throws Exception + */ + public function userHasUploadedFollowingFilesWithContent( + string $user, + ?string $content, + TableNode $table + ):array { + $this->verifyTableNodeColumns($table, ["path"]); + $files = $table->getHash(); + + $fileIds = []; + foreach ($files as $destination) { + $fileId = $this->userHasUploadedAFileWithContentTo($user, $content, $destination["path"])[0]; + \array_push($fileIds, $fileId); + } + return $fileIds; + } + + /** + * @When /^user "([^"]*)" downloads the following files using the WebDAV API$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userDownloadsFollowingFiles( + string $user, + TableNode $table + ):void { + $this->verifyTableNodeColumns($table, ["path"]); + $files = $table->getHash(); + $this->emptyLastHTTPStatusCodesArray(); + foreach ($files as $fileName) { + $this->downloadFileAsUserUsingPassword($user, $fileName["path"]); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @When user :user uploads a file with content :content and mtime :mtime to :destination using the WebDAV API + * + * @param string $user + * @param string|null $content + * @param string $mtime + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userUploadsAFileWithContentAndMtimeTo( + string $user, + ?string $content, + string $mtime, + string $destination + ):void { + $user = $this->getActualUsername($user); + $mtime = new DateTime($mtime); + $mtime = $mtime->format('U'); + $this->makeDavRequest( + $user, + "PUT", + $destination, + ["X-OC-Mtime" => $mtime], + $content + ); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file '$destination' with mtime $mtime for user '$user'" + ); + } + + /** + * @When user :user uploads file with checksum :checksum and content :content to :destination using the WebDAV API + * + * @param string $user + * @param string $checksum + * @param string|null $content + * @param string $destination + * + * @return void + */ + public function userUploadsAFileWithChecksumAndContentTo( + string $user, + string $checksum, + ?string $content, + string $destination + ):void { + $this->pauseUploadDelete(); + $this->response = $this->makeDavRequest( + $user, + "PUT", + $destination, + ['OC-Checksum' => $checksum], + $content + ); + $this->lastUploadDeleteTime = \time(); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given user :user has uploaded file with checksum :checksum and content :content to :destination + * + * @param string $user + * @param string $checksum + * @param string|null $content + * @param string $destination + * + * @return void + */ + public function userHasUploadedAFileWithChecksumAndContentTo( + string $user, + string $checksum, + ?string $content, + string $destination + ):void { + $this->userUploadsAFileWithChecksumAndContentTo( + $user, + $checksum, + $content, + $destination + ); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload file with checksum '$checksum' to '$destination' for user '$user'" + ); + } + + /** + * @Then /^user "([^"]*)" should be able to delete (file|folder|entry) "([^"]*)"$/ + * + * @param string $user + * @param string $entry + * @param string $source + * + * @return void + * @throws Exception + */ + public function userShouldBeAbleToDeleteEntry(string $user, string $entry, string $source):void { + $user = $this->getActualUsername($user); + $this->asFileOrFolderShouldExist($user, $entry, $source); + $this->userDeletesFile($user, $source); + $this->asFileOrFolderShouldNotExist($user, $entry, $source); + } + + /** + * @Then /^user "([^"]*)" should not be able to delete (file|folder|entry) "([^"]*)"$/ + * + * @param string $user + * @param string $entry + * @param string $source + * + * @return void + * @throws Exception + */ + public function theUserShouldNotBeAbleToDeleteEntry(string $user, string $entry, string $source):void { + $this->asFileOrFolderShouldExist($user, $entry, $source); + $this->userDeletesFile($user, $source); + $this->asFileOrFolderShouldExist($user, $entry, $source); + } + + /** + * @Given file :file has been deleted for user :user + * + * @param string $file + * @param string $user + * + * @return void + */ + public function fileHasBeenDeleted(string $file, string $user):void { + $this->userHasDeletedFile($user, "deleted", "file", $file); + } + + /** + * @When /^user "([^"]*)" (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $file + * + * @return void + */ + public function userDeletesFile(string $user, string $file):void { + $user = $this->getActualUsername($user); + $this->pauseUploadDelete(); + $this->response = $this->makeDavRequest($user, 'DELETE', $file, []); + $this->lastUploadDeleteTime = \time(); + $this->pushToLastHttpStatusCodesArray((string) $this->getResponse()->getStatusCode()); + } + + /** + * @Given /^user "([^"]*)" has (deleted|unshared) (file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $deletedOrUnshared + * @param string $fileOrFolder + * @param string $entry + * + * @return void + */ + public function userHasDeletedFile(string $user, string $deletedOrUnshared, string $fileOrFolder, string $entry):void { + $user = $this->getActualUsername($user); + $this->userDeletesFile($user, $entry); + // If the file or folder was there and got deleted then we get a 204 + // That is good and the expected status + // If the file or folder was already not there then then we get a 404 + // That is not expected. Scenarios that use "Given user has deleted..." + // should only be using such steps when it is a file that exists and needs + // to be deleted. + if ($deletedOrUnshared === "deleted") { + $deleteText = "delete"; + } else { + $deleteText = "unshare"; + } + + $this->theHTTPStatusCodeShouldBe( + ["204"], + "HTTP status code was not 204 while trying to $deleteText $fileOrFolder '$entry' for user '$user'" + ); + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @Given /^user "([^"]*)" has (deleted|unshared) the following (files|folders|resources)$/ + * + * @param string $user + * @param string $deletedOrUnshared + * @param string $fileOrFolder + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userHasDeletedFollowingFiles(string $user, string $deletedOrUnshared, string $fileOrFolder, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $file) { + $this->userHasDeletedFile($user, $deletedOrUnshared, $fileOrFolder, $file["path"]); + } + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @When /^user "([^"]*)" (?:deletes|unshares) the following (?:files|folders)$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userDeletesFollowingFiles(string $user, TableNode $table):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $file) { + $this->userDeletesFile($user, $file["path"]); + $this->pushToLastStatusCodesArrays(); + } + } + + /** + * @When /^the user (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * + * @param string $file + * + * @return void + */ + public function theUserDeletesFile(string $file):void { + $this->userDeletesFile($this->getCurrentUser(), $file); + } + + /** + * @Given /^the user has (deleted|unshared) (file|folder) "([^"]*)"$/ + * + * @param string $deletedOrUnshared + * @param string $fileOrFolder + * @param string $file + * + * @return void + */ + public function theUserHasDeletedFile(string $deletedOrUnshared, string $fileOrFolder, string $file):void { + $this->userHasDeletedFile($this->getCurrentUser(), $deletedOrUnshared, $fileOrFolder, $file); + } + + /** + * @When /^user "([^"]*)" (?:deletes|unshares) these (?:files|folders|entries) without delays using the WebDAV API$/ + * + * @param string $user + * @param TableNode $table of files or folders to delete + * + * @return void + * @throws Exception + */ + public function userDeletesFilesFoldersWithoutDelays(string $user, TableNode $table):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($table, 1); + foreach ($table->getTable() as $entry) { + $entryName = $entry[0]; + $this->response = $this->makeDavRequest($user, 'DELETE', $entryName, []); + $this->pushToLastStatusCodesArrays(); + } + $this->lastUploadDeleteTime = \time(); + } + + /** + * @When /^the user (?:deletes|unshares) these (?:files|folders|entries) without delays using the WebDAV API$/ + * + * @param TableNode $table of files or folders to delete + * + * @return void + * @throws Exception + */ + public function theUserDeletesFilesFoldersWithoutDelays(TableNode $table):void { + $this->userDeletesFilesFoldersWithoutDelays($this->getCurrentUser(), $table); + } + + /** + * @When /^user "([^"]*)" on "(LOCAL|REMOTE)" (?:deletes|unshares) (?:file|folder) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $server + * @param string $file + * + * @return void + */ + public function userOnDeletesFile(string $user, string $server, string $file):void { + $previousServer = $this->usingServer($server); + $this->userDeletesFile($user, $file); + $this->usingServer($previousServer); + } + + /** + * @Given /^user "([^"]*)" on "(LOCAL|REMOTE)" has (deleted|unshared) (file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $server + * @param string $deletedOrUnshared + * @param string $fileOrFolder + * @param string $entry + * + * @return void + */ + public function userOnHasDeletedFile(string $user, string $server, string $deletedOrUnshared, string $fileOrFolder, string $entry):void { + $this->userOnDeletesFile($user, $server, $entry); + // If the file was there and got deleted then we get a 204 + // If the file was already not there then then get a 404 + // Either way, the outcome of the "given" step is OK + if ($deletedOrUnshared === "deleted") { + $deleteText = "delete"; + } else { + $deleteText = "unshare"; + } + + $this->theHTTPStatusCodeShouldBe( + ["204", "404"], + "HTTP status code was not 204 or 404 while trying to $deleteText $fileOrFolder '$entry' for user '$user' on server '$server'" + ); + } + + /** + * @When user :user creates folder :destination using the WebDAV API + * + * @param string $user + * @param string $destination + * + * @return void + * @throws JsonException | GuzzleException + */ + public function userCreatesFolder(string $user, string $destination):void { + $user = $this->getActualUsername($user); + $destination = '/' . \ltrim($destination, '/'); + $this->response = $this->makeDavRequest( + $user, + "MKCOL", + $destination, + [] + ); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + $this->pushToLastHttpStatusCodesArray(); + } + + /** + * @Given user :user has created folder :destination + * + * @param string $user + * @param string $destination + * + * @return void + * @throws JsonException + * @throws GuzzleException + */ + public function userHasCreatedFolder(string $user, string $destination):void { + $user = $this->getActualUsername($user); + $this->userCreatesFolder($user, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to create folder '$destination' for user '$user'" + ); + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @Given admin has created folder :destination + * + * @param string $destination + * + * @return void + * @throws JsonException + * @throws GuzzleException + */ + public function adminHasCreatedFolder(string $destination):void { + $admin = $this->getAdminUsername(); + Assert::assertEquals( + "admin", + $admin, + __METHOD__ . "The provided user is not admin but '" . $admin . "'" + ); + $this->userCreatesFolder($admin, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to create folder '$destination' for admin '$admin'" + ); + $this->adminResources[] = $destination; + $this->emptyLastHTTPStatusCodesArray(); + } + + /** + * @Given /^user "([^"]*)" has created the following folders$/ + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function userHasCreatedFollowingFolders(string $user, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["path"]); + $paths = $table->getHash(); + + foreach ($paths as $path) { + $this->userHasCreatedFolder($user, $path["path"]); + } + } + + /** + * @When the user creates folder :destination using the WebDAV API + * + * @param string $destination + * + * @return void + */ + public function theUserCreatesFolder(string $destination):void { + $this->userCreatesFolder($this->getCurrentUser(), $destination); + } + + /** + * @Given the user has created folder :destination + * + * @param string $destination + * + * @return void + */ + public function theUserHasCreatedFolder(string $destination):void { + $this->theUserCreatesFolder($destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to create folder '$destination'" + ); + } + + /** + * @Then user :user should be able to create folder :destination + * + * @param string $user + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userShouldBeAbleToCreateFolder(string $user, string $destination):void { + $this->userHasCreatedFolder($user, $destination); + $this->asFileOrFolderShouldExist( + $user, + "folder", + $destination + ); + } + + /** + * @Then user :user should not be able to create folder :destination + * + * @param string $user + * @param string $destination + * + * @return void + * @throws Exception + */ + public function userShouldNotBeAbleToCreateFolder(string $user, string $destination):void { + $user = $this->getActualUsername($user); + $this->userCreatesFolder($user, $destination); + $this->theHTTPStatusCodeShouldBeFailure(); + $this->asFileOrFolderShouldNotExist( + $user, + "folder", + $destination + ); + } + + /** + * Old style chunking upload + * + * @When user :user uploads the following :total chunks to :file with old chunking and using the WebDAV API + * + * @param string $user + * @param string $total + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content with column headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | followed by second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + * @throws Exception + */ + public function userUploadsTheFollowingTotalChunksUsingOldChunking( + string $user, + string $total, + string $file, + TableNode $chunkDetails + ):void { + $this->verifyTableNodeColumns($chunkDetails, ['number', 'content']); + foreach ($chunkDetails->getHash() as $chunkDetail) { + $chunkNumber = (int)$chunkDetail['number']; + $chunkContent = $chunkDetail['content']; + $this->userUploadsChunkedFile($user, $chunkNumber, (int) $total, $chunkContent, $file); + } + } + + /** + * Old style chunking upload + * + * @Given user :user has uploaded the following :total chunks to :file with old chunking + * + * @param string $user + * @param string $total + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content with following headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | followed by second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + * @throws Exception + */ + public function userHasUploadedTheFollowingTotalChunksUsingOldChunking( + string $user, + string $total, + string $file, + TableNode $chunkDetails + ):void { + $this->verifyTableNodeColumns($chunkDetails, ['number', 'content']); + foreach ($chunkDetails->getHash() as $chunkDetail) { + $chunkNumber = (int) $chunkDetail['number']; + $chunkContent = $chunkDetail['content']; + $this->userHasUploadedChunkedFile($user, $chunkNumber, (int) $total, $chunkContent, $file); + } + } + + /** + * Old style chunking upload + * + * @When user :user uploads the following chunks to :file with old chunking and using the WebDAV API + * + * @param string $user + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content with column headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | followed by second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + * @throws Exception + */ + public function userUploadsTheFollowingChunksUsingOldChunking( + string $user, + string $file, + TableNode $chunkDetails + ):void { + $total = \count($chunkDetails->getHash()); + $this->userUploadsTheFollowingTotalChunksUsingOldChunking( + $user, + (string) $total, + $file, + $chunkDetails + ); + } + + /** + * Old style chunking upload + * + * @Given user :user has uploaded the following chunks to :file with old chunking + * + * @param string $user + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content with headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | followed by second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + * @throws Exception + */ + public function userHasUploadedTheFollowingChunksUsingOldChunking( + string $user, + string $file, + TableNode $chunkDetails + ):void { + $total = \count($chunkDetails->getRows()); + $this->userHasUploadedTheFollowingTotalChunksUsingOldChunking( + $user, + (string) $total, + $file, + $chunkDetails + ); + } + + /** + * Old style chunking upload + * + * @When user :user uploads chunk file :num of :total with :data to :destination using the WebDAV API + * + * @param string $user + * @param int $num + * @param int $total + * @param string|null $data + * @param string $destination + * + * @return void + */ + public function userUploadsChunkedFile( + string $user, + int $num, + int $total, + ?string $data, + string $destination + ):void { + $user = $this->getActualUsername($user); + $num -= 1; + $file = "$destination-chunking-42-$total-$num"; + $this->pauseUploadDelete(); + $this->response = $this->makeDavRequest( + $user, + 'PUT', + $file, + ['OC-Chunked' => '1'], + $data, + "uploads" + ); + $this->lastUploadDeleteTime = \time(); + } + + /** + * Old style chunking upload + * + * @Given user :user has uploaded chunk file :num of :total with :data to :destination + * + * @param string $user + * @param int|null $num + * @param int|null $total + * @param string|null $data + * @param string $destination + * + * @return void + */ + public function userHasUploadedChunkedFile( + string $user, + ?int $num, + ?int $total, + ?string $data, + string $destination + ):void { + $user = $this->getActualUsername($user); + $this->userUploadsChunkedFile($user, $num, $total, $data, $destination); + $this->theHTTPStatusCodeShouldBe( + ["201", "204"], + "HTTP status code was not 201 or 204 while trying to upload chunk $num of $total to file '$destination' for user '$user'" + ); + } + + /** + * New style chunking upload + * + * @When /^user "([^"]*)" uploads the following chunks\s?(asynchronously|) to "([^"]*)" with new chunking and using the WebDAV API$/ + * + * @param string $user + * @param string $type "asynchronously" or empty + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content, with headings e.g. + * | number | content | + * | 1 | first data | + * | 2 | second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + */ + public function userUploadsTheFollowingChunksUsingNewChunking( + string $user, + string $type, + string $file, + TableNode $chunkDetails + ):void { + $this->uploadTheFollowingChunksUsingNewChunking( + $user, + $type, + $file, + $chunkDetails + ); + } + + /** + * New style chunking upload + * + * @Given /^user "([^"]*)" has uploaded the following chunks\s?(asynchronously|) to "([^"]*)" with new chunking$/ + * + * @param string $user + * @param string $type "asynchronously" or empty + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content without column headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | followed by second data | + * Chunks may be numbered out-of-order if desired. + * + * @return void + */ + public function userHasUploadedTheFollowingChunksUsingNewChunking( + string $user, + string $type, + string $file, + TableNode $chunkDetails + ):void { + $this->uploadTheFollowingChunksUsingNewChunking( + $user, + $type, + $file, + $chunkDetails, + true + ); + } + + /** + * New style chunking upload + * + * @param string $user + * @param string $type "asynchronously" or empty + * @param string $file + * @param TableNode $chunkDetails table of 2 columns, chunk number and chunk + * content with column headings, e.g. + * | number | content | + * | 1 | first data | + * | 2 | second data | + * Chunks may be numbered out-of-order if desired. + * @param bool $checkActions + * + * @return void + * @throws Exception + */ + public function uploadTheFollowingChunksUsingNewChunking( + string $user, + string $type, + string $file, + TableNode $chunkDetails, + bool $checkActions = false + ):void { + $user = $this->getActualUsername($user); + $async = false; + if ($type === "asynchronously") { + $async = true; + } + $this->verifyTableNodeColumns($chunkDetails, ["number", "content"]); + $this->userUploadsChunksUsingNewChunking( + $user, + $file, + 'chunking-42', + $chunkDetails->getHash(), + $async, + $checkActions + ); + } + + /** + * New style chunking upload + * + * @param string $user + * @param string $file + * @param string $chunkingId + * @param array $chunkDetails of chunks of the file. Each array entry is + * itself an array of 2 items: + * [number] the chunk number + * [content] data content of the chunk + * Chunks may be numbered out-of-order if desired. + * @param bool $async use asynchronous MOVE at the end or not + * @param bool $checkActions + * + * @return void + */ + public function userUploadsChunksUsingNewChunking( + string $user, + string $file, + string $chunkingId, + array $chunkDetails, + bool $async = false, + bool $checkActions = false + ):void { + $this->pauseUploadDelete(); + if ($checkActions) { + $this->userHasCreatedANewChunkingUploadWithId($user, $chunkingId); + } else { + $this->userCreatesANewChunkingUploadWithId($user, $chunkingId); + } + foreach ($chunkDetails as $chunkDetail) { + $chunkNumber = (int)$chunkDetail['number']; + $chunkContent = $chunkDetail['content']; + if ($checkActions) { + $this->userHasUploadedNewChunkFileOfWithToId($user, $chunkNumber, $chunkContent, $chunkingId); + } else { + $this->userUploadsNewChunkFileOfWithToId($user, $chunkNumber, $chunkContent, $chunkingId); + } + } + $headers = []; + if ($async === true) { + $headers = ['OC-LazyOps' => 'true']; + } + $this->moveNewDavChunkToFinalFile($user, $chunkingId, $file, $headers); + if ($checkActions) { + $this->theHTTPStatusCodeShouldBeSuccess(); + } + $this->lastUploadDeleteTime = \time(); + } + + /** + * @When user :user creates a new chunking upload with id :id using the WebDAV API + * + * @param string $user + * @param string $id + * + * @return void + */ + public function userCreatesANewChunkingUploadWithId(string $user, string $id):void { + $user = $this->getActualUsername($user); + $destination = "/uploads/$user/$id"; + $this->response = $this->makeDavRequest( + $user, + 'MKCOL', + $destination, + [], + null, + "uploads" + ); + } + + /** + * @Given user :user has created a new chunking upload with id :id + * + * @param string $user + * @param string $id + * + * @return void + */ + public function userHasCreatedANewChunkingUploadWithId(string $user, string $id):void { + $this->userCreatesANewChunkingUploadWithId($user, $id); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When user :user uploads new chunk file :num with :data to id :id using the WebDAV API + * + * @param string $user + * @param int $num + * @param string|null $data + * @param string $id + * + * @return void + */ + public function userUploadsNewChunkFileOfWithToId(string $user, int $num, ?string $data, string $id):void { + $user = $this->getActualUsername($user); + $destination = "/uploads/$user/$id/$num"; + $this->response = $this->makeDavRequest( + $user, + 'PUT', + $destination, + [], + $data, + "uploads" + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given user :user has uploaded new chunk file :num with :data to id :id + * + * @param string $user + * @param int $num + * @param string|null $data + * @param string $id + * + * @return void + */ + public function userHasUploadedNewChunkFileOfWithToId(string $user, int $num, ?string $data, string $id):void { + $this->userUploadsNewChunkFileOfWithToId($user, $num, $data, $id); + $this->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When /^user "([^"]*)" moves new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * + * @return void + */ + public function userMovesNewChunkFileWithIdToMychunkedfile( + string $user, + string $id, + string $type, + string $dest + ):void { + $headers = []; + if ($type === "asynchronously") { + $headers = ['OC-LazyOps' => 'true']; + } + $this->moveNewDavChunkToFinalFile($user, $id, $dest, $headers); + } + + /** + * @Given /^user "([^"]*)" has moved new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)"$/ + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * + * @return void + */ + public function userHasMovedNewChunkFileWithIdToMychunkedfile( + string $user, + string $id, + string $type, + string $dest + ):void { + $this->userMovesNewChunkFileWithIdToMychunkedfile($user, $id, $type, $dest); + $this->theHTTPStatusCodeShouldBe("201"); + } + + /** + * @When user :user cancels chunking-upload with id :id using the WebDAV API + * + * @param string $user + * @param string $id + * + * @return void + */ + public function userCancelsUploadWithId( + string $user, + string $id + ):void { + $this->deleteUpload($user, $id, []); + } + + /** + * @Given user :user has canceled new chunking-upload with id :id + * + * @param string $user + * @param string $id + * + * @return void + */ + public function userHasCanceledUploadWithId( + string $user, + string $id + ):void { + $this->userCancelsUploadWithId($user, $id); + $this->theHTTPStatusCodeShouldBe("201"); + } + + /** + * @When /^user "([^"]*)" moves new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" with size (.*) using the WebDAV API$/ + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * @param int $size + * + * @return void + */ + public function userMovesNewChunkFileWithIdToMychunkedfileWithSize( + string $user, + string $id, + string $type, + string $dest, + int $size + ):void { + $headers = ['OC-Total-Length' => $size]; + if ($type === "asynchronously") { + $headers['OC-LazyOps'] = 'true'; + } + $this->moveNewDavChunkToFinalFile( + $user, + $id, + $dest, + $headers + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has moved new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" with size (.*)$/ + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * @param int $size + * + * @return void + */ + public function userHasMovedNewChunkFileWithIdToMychunkedfileWithSize( + string $user, + string $id, + string $type, + string $dest, + int $size + ):void { + $this->userMovesNewChunkFileWithIdToMychunkedfileWithSize( + $user, + $id, + $type, + $dest, + $size + ); + $this->theHTTPStatusCodeShouldBe("201"); + } + + /** + * @When /^user "([^"]*)" moves new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" with checksum "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * @param string $checksum + * + * @return void + */ + public function userMovesNewChunkFileWithIdToMychunkedfileWithChecksum( + string $user, + string $id, + string $type, + string $dest, + string $checksum + ):void { + $headers = ['OC-Checksum' => $checksum]; + if ($type === "asynchronously") { + $headers['OC-LazyOps'] = 'true'; + } + $this->moveNewDavChunkToFinalFile( + $user, + $id, + $dest, + $headers + ); + $this->pushToLastStatusCodesArrays(); + } + + /** + * @Given /^user "([^"]*)" has moved new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" with checksum "([^"]*)" + * + * @param string $user + * @param string $id + * @param string $type "asynchronously" or empty + * @param string $dest + * @param string $checksum + * + * @return void + */ + public function userHasMovedNewChunkFileWithIdToMychunkedfileWithChecksum( + string $user, + string $id, + string $type, + string $dest, + string $checksum + ):void { + $this->userMovesNewChunkFileWithIdToMychunkedfileWithChecksum( + $user, + $id, + $type, + $dest, + $checksum + ); + $this->theHTTPStatusCodeShouldBe("201"); + } + + /** + * Move chunked new DAV file to final file + * + * @param string $user user + * @param string $id upload id + * @param string $destination destination path + * @param array $headers extra headers + * + * @return void + */ + private function moveNewDavChunkToFinalFile(string $user, string $id, string $destination, array $headers):void { + $user = $this->getActualUsername($user); + $source = "/uploads/$user/$id/.file"; + $headers['Destination'] = $this->destinationHeaderValue( + $user, + $destination + ); + + $this->response = $this->makeDavRequest( + $user, + 'MOVE', + $source, + $headers, + null, + "uploads" + ); + } + + /** + * Delete chunked-upload directory + * + * @param string $user user + * @param string $id upload id + * @param array $headers extra headers + * + * @return void + */ + private function deleteUpload(string $user, string $id, array $headers) { + $source = "/uploads/$user/$id"; + $this->response = $this->makeDavRequest( + $user, + 'DELETE', + $source, + $headers, + null, + "uploads" + ); + } + + /** + * URL encodes the given path but keeps the slashes + * + * @param string $path to encode + * + * @return string encoded path + */ + public function encodePath(string $path):string { + // slashes need to stay + $encodedPath = \str_replace('%2F', '/', \rawurlencode($path)); + // in ocis even brackets are encoded + if (OcisHelper::isTestingOnOcisOrReva()) { + return $encodedPath; + } + // do not encode '(' and ')' for oc10 + $encodedPath = \str_replace('%28', '(', $encodedPath); + $encodedPath = \str_replace('%29', ')', $encodedPath); + return $encodedPath; + } + + /** + * @When an unauthenticated client connects to the DAV endpoint using the WebDAV API + * + * @return void + */ + public function connectingToDavEndpoint():void { + $this->response = $this->makeDavRequest( + null, + 'PROPFIND', + '', + [] + ); + } + + /** + * @Given an unauthenticated client has connected to the DAV endpoint + * + * @return void + */ + public function hasConnectedToDavEndpoint():void { + $this->connectingToDavEndpoint(); + $this->theHTTPStatusCodeShouldBe("401"); + } + + /** + * @Then there should be no duplicate headers + * + * @return void + * @throws Exception + */ + public function thereAreNoDuplicateHeaders():void { + $headers = $this->response->getHeaders(); + foreach ($headers as $headerName => $headerValues) { + // if a header has multiple values, they must be different + if (\count($headerValues) > 1 + && \count(\array_unique($headerValues)) < \count($headerValues) + ) { + throw new Exception("Duplicate header found: $headerName"); + } + } + } + + /** + * @Then the following headers should not be set + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theFollowingHeadersShouldNotBeSet(TableNode $table):void { + $this->verifyTableNodeColumns( + $table, + ['header'] + ); + foreach ($table->getColumnsHash() as $header) { + $headerName = $header['header']; + $headerValue = $this->response->getHeader($headerName); + //Note: getHeader returns an empty array if the named header does not exist + if (isset($headerValue[0])) { + $headerValue0 = $headerValue[0]; + } else { + $headerValue0 = ''; + } + Assert::assertEmpty( + $headerValue, + "header $headerName should not exist " . + "but does and is set to $headerValue0" + ); + } + } + + /** + * @Then the following headers should match these regular expressions + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function headersShouldMatchRegularExpressions(TableNode $table):void { + $this->verifyTableNodeColumnsCount($table, 2); + foreach ($table->getTable() as $header) { + $headerName = $header[0]; + $expectedHeaderValue = $header[1]; + $expectedHeaderValue = $this->substituteInLineCodes( + $expectedHeaderValue, + null, + ['preg_quote' => ['/']] + ); + + $returnedHeaders = $this->response->getHeader($headerName); + $returnedHeader = $returnedHeaders[0]; + Assert::assertNotFalse( + (bool) \preg_match($expectedHeaderValue, $returnedHeader), + "'$expectedHeaderValue' does not match '$returnedHeader'" + ); + } + } + + /** + * @Then /^if the HTTP status code was "([^"]*)" then the following headers should match these regular expressions$/ + * + * @param int $statusCode + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function statusCodeShouldMatchTheseRegularExpressions(int $statusCode, TableNode $table):void { + $actualStatusCode = $this->response->getStatusCode(); + if ($actualStatusCode === $statusCode) { + $this->headersShouldMatchRegularExpressions($table); + } + } + + /** + * @Then the following headers should match these regular expressions for user :user + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function headersShouldMatchRegularExpressionsForUser(string $user, TableNode $table):void { + $this->verifyTableNodeColumnsCount($table, 2); + $user = $this->getActualUsername($user); + foreach ($table->getTable() as $header) { + $headerName = $header[0]; + $expectedHeaderValue = $header[1]; + $expectedHeaderValue = $this->substituteInLineCodes( + $expectedHeaderValue, + $user, + ['preg_quote' => ['/']] + ); + + $returnedHeaders = $this->response->getHeader($headerName); + $returnedHeader = $returnedHeaders[0]; + Assert::assertNotFalse( + (bool) \preg_match($expectedHeaderValue, $returnedHeader), + "'$expectedHeaderValue' does not match '$returnedHeader'" + ); + } + } + + /** + * @When /^user "([^"]*)" deletes everything from folder "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $folder + * @param bool $checkEachDelete + * + * @return void + * @throws Exception + */ + public function userDeletesEverythingInFolder( + string $user, + string $folder, + bool $checkEachDelete = false + ):void { + $user = $this->getActualUsername($user); + $responseXmlObject = $this->listFolderAndReturnResponseXml( + $user, + $folder, + '1' + ); + $elementList = $responseXmlObject->xpath("//d:response/d:href"); + if (\is_array($elementList) && \count($elementList)) { + \array_shift($elementList); //don't delete the folder itself + $davPrefix = "/" . $this->getFullDavFilesPath($user); + foreach ($elementList as $element) { + $element = \substr((string)$element, \strlen($davPrefix)); + if ($checkEachDelete) { + $this->userHasDeletedFile($user, "deleted", "file", $element); + } else { + $this->userDeletesFile($user, $element); + } + } + } + } + + /** + * @Given /^user "([^"]*)" has deleted everything from folder "([^"]*)"$/ + * + * @param string $user + * @param string $folder + * + * @return void + * @throws Exception + */ + public function userHasDeletedEverythingInFolder(string $user, string $folder):void { + $this->userDeletesEverythingInFolder($user, $folder, true); + } + + /** + * @When user :user downloads the preview of :path with width :width and height :height using the WebDAV API + * + * @param string $user + * @param string $path + * @param string $width + * @param string $height + * + * @return void + */ + public function downloadPreviewOfFiles(string $user, string $path, string $width, string $height):void { + $this->downloadPreviews( + $user, + $path, + null, + $width, + $height + ); + } + + /** + * @When user :user1 downloads the preview of :path of :user2 with width :width and height :height using the WebDAV API + * + * @param string $user1 + * @param string $path + * @param string $doDavRequestAsUser + * @param string $width + * @param string $height + * + * @return void + */ + public function downloadPreviewOfOtherUser(string $user1, string $path, string $doDavRequestAsUser, string $width, string $height):void { + $this->downloadPreviews( + $user1, + $path, + $doDavRequestAsUser, + $width, + $height + ); + } + + /** + * @Then the downloaded image for user :user should be :width pixels wide and :height pixels high + * + * @param string $user + * @param string $width + * @param string $height + * + * @return void + */ + public function imageDimensionsForAUserShouldBe(string $user, string $width, string $height):void { + if ($this->userResponseBodyContents[$user] === null) { + $this->userResponseBodyContents[$user] = $this->response->getBody()->getContents(); + } + $size = \getimagesizefromstring($this->userResponseBodyContents[$user]); + Assert::assertNotFalse($size, "could not get size of image"); + Assert::assertEquals($width, $size[0], "width not as expected"); + Assert::assertEquals($height, $size[1], "height not as expected"); + } + + /** + * @Then the downloaded image should be :width pixels wide and :height pixels high + * + * @param string $width + * @param string $height + * + * @return void + */ + public function imageDimensionsShouldBe(string $width, string $height): void { + if ($this->responseBodyContent === null) { + $this->responseBodyContent = $this->response->getBody()->getContents(); + } + $size = \getimagesizefromstring($this->responseBodyContent); + Assert::assertNotFalse($size, "could not get size of image"); + Assert::assertEquals($width, $size[0], "width not as expected"); + Assert::assertEquals($height, $size[1], "height not as expected"); + } + + /** + * @Then the requested JPEG image should have a quality value of :size + * + * @param string $value + * + * @return void + */ + public function jpgQualityValueShouldBe(string $value): void { + $this->responseBodyContent = $this->response->getBody()->getContents(); + // quality value is embedded in the string content for JPEG images + $qualityString = "quality = $value"; + Assert::assertStringContainsString($qualityString, $this->responseBodyContent); + } + + /** + * @Then the downloaded preview content should match with :preview fixtures preview content + * + * @param string $filename relative path from fixtures directory + * + * @return void + * @throws Exception + */ + public function theDownloadedPreviewContentShouldMatchWithFixturesPreviewContentFor(string $filename):void { + $expectedPreview = \file_get_contents(__DIR__ . "/../../fixtures/" . $filename); + Assert::assertEquals($expectedPreview, $this->responseBodyContent); + } + + /** + * @Given user :user has downloaded the preview of :path with width :width and height :height + * + * @param string $user + * @param string $path + * @param string $width + * @param string $height + * + * @return void + */ + public function userDownloadsThePreviewOfWithWidthAndHeight(string $user, string $path, string $width, string $height):void { + $this->downloadPreviewOfFiles($user, $path, $width, $height); + $this->theHTTPStatusCodeShouldBe(200); + $this->imageDimensionsShouldBe($width, $height); + // save response to user response dictionary for further comparisons + $this->userResponseBodyContents[$user] = $this->responseBodyContent; + } + + /** + * @Then as user :user the preview of :path with width :width and height :height should have been changed + * + * @param string $user + * @param string $path + * @param string $width + * @param string $height + * + * @return void + */ + public function asUserThePreviewOfPathWithHeightAndWidthShouldHaveBeenChanged(string $user, string $path, string $width, string $height):void { + $this->downloadPreviewOfFiles($user, $path, $width, $height); + $this->theHTTPStatusCodeShouldBe(200); + $newResponseBodyContents = $this->response->getBody()->getContents(); + Assert::assertNotEquals( + $newResponseBodyContents, + // different users can download files before and after an update is made to a file + // previous response content is fetched from user response body content array for that user + $this->userResponseBodyContents[$user], + __METHOD__ . " previous and current previews content is same but expected to be different", + ); + // update the saved content for the next comparison + $this->userResponseBodyContents[$user] = $newResponseBodyContents; + } + + /** + * @param string $user + * @param string $path + * + * @return string|null + */ + public function getFileIdForPath(string $user, string $path): ?string { + $user = $this->getActualUsername($user); + try { + return WebDavHelper::getFileIdForPath( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + $path, + $this->getStepLineRef(), + $this->getDavPathVersion() + ); + } catch (Exception $e) { + return null; + } + } + + /** + * @Given /^user "([^"]*)" has stored id of (file|folder) "([^"]*)"$/ + * @When /^user "([^"]*)" stores id of (file|folder) "([^"]*)"$/ + * + * @param string $user + * @param string $fileOrFolder + * @param string $path + * + * @return void + */ + public function userStoresFileIdForPath(string $user, string $fileOrFolder, string $path):void { + $this->storedFileID = $this->getFileIdForPath($user, $path); + } + + /** + * @Then /^user "([^"]*)" (file|folder) "([^"]*)" should have the previously stored id$/ + * + * @param string $user + * @param string $fileOrFolder + * @param string $path + * + * @return void + */ + public function userFileShouldHaveStoredId(string $user, string $fileOrFolder, string $path):void { + $user = $this->getActualUsername($user); + $currentFileID = $this->getFileIdForPath($user, $path); + Assert::assertEquals( + $currentFileID, + $this->storedFileID, + __METHOD__ + . " User '$user' $fileOrFolder '$path' does not have the previously stored id '{$this->storedFileID}', but has '$currentFileID'." + ); + } + + /** + * @Then /^the (?:Cal|Card)?DAV (exception|message|reason) should be "([^"]*)"$/ + * + * @param string $element exception|message|reason + * @param string $message + * + * @return void + * @throws Exception + */ + public function theDavElementShouldBe(string $element, string $message):void { + WebDavAssert::assertDavResponseElementIs( + $element, + $message, + $this->responseXml, + __METHOD__ + ); + } + + /** + * @param string $shouldOrNot (not|) + * @param TableNode $expectedFiles + * @param string|null $user + * @param string|null $method + * @param string|null $folderpath + * + * @return void + * @throws GuzzleException + */ + public function propfindResultShouldContainEntries( + string $shouldOrNot, + TableNode $expectedFiles, + ?string $user = null, + ?string $method = 'REPORT', + ?string $folderpath = '' + ):void { + $this->verifyTableNodeColumnsCount($expectedFiles, 1); + $elementRows = $expectedFiles->getRows(); + $should = ($shouldOrNot !== "not"); + foreach ($elementRows as $expectedFile) { + $fileFound = $this->findEntryFromPropfindResponse( + $expectedFile[0], + $user, + $method, + "files", + $folderpath + ); + if ($should) { + Assert::assertNotEmpty( + $fileFound, + "response does not contain the entry '$expectedFile[0]'" + ); + } else { + Assert::assertFalse( + $fileFound, + "response does contain the entry '$expectedFile[0]' but should not" + ); + } + } + } + + /** + * @Then /^the (?:propfind|search) result of user "([^"]*)" should (not|)\s?contain these (?:files|entries):$/ + * + * @param string $user + * @param string $shouldOrNot (not|) + * @param TableNode $expectedFiles + * + * @return void + * @throws Exception + */ + public function thePropfindResultShouldContainEntries( + string $user, + string $shouldOrNot, + TableNode $expectedFiles + ):void { + $user = $this->getActualUsername($user); + $this->propfindResultShouldContainEntries( + $shouldOrNot, + $expectedFiles, + $user + ); + } + + /** + * @Then /^the (?:propfind|search) result of user "([^"]*)" should not contain any (?:files|entries)$/ + * + * @param string $user + * + * @return void + * @throws Exception + */ + public function thePropfindResultShouldNotContainAnyEntries( + string $user + ):void { + $multistatusResults = $this->getMultistatusResultFromPropfindResult($user); + Assert::assertEmpty($multistatusResults, 'The propfind response was expected to be empty but was not'); + } + + /** + * @Then /^the (?:propfind|search) result of user "([^"]*)" should contain only these (?:files|entries):$/ + * + * @param string $user + * @param TableNode $expectedFiles + * + * @return void + * @throws Exception + */ + public function thePropfindResultShouldContainOnlyEntries( + string $user, + TableNode $expectedFiles + ):void { + $user = $this->getActualUsername($user); + + Assert::assertEquals( + \count($expectedFiles->getTable()), + $this->getNumberOfEntriesInPropfindResponse( + $user + ), + "The number of elements in the response doesn't matches with expected number of elements" + ); + $this->propfindResultShouldContainEntries( + '', + $expectedFiles, + $user + ); + } + + /** + * @Then the propfind/search result should contain :numFiles files/entries + * + * @param int $numFiles + * + * @return void + */ + public function propfindResultShouldContainNumEntries(int $numFiles):void { + //if we are using that step the second time in a scenario e.g. 'But ... should not' + //then don't parse the result again, because the result in a ResponseInterface + if (empty($this->responseXml)) { + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + } + Assert::assertIsArray( + $this->responseXml, + __METHOD__ . " responseXml is not an array" + ); + Assert::assertArrayHasKey( + "value", + $this->responseXml, + __METHOD__ . " responseXml does not have key 'value'" + ); + $multistatusResults = $this->responseXml["value"]; + if ($multistatusResults === null) { + $multistatusResults = []; + } + Assert::assertEquals( + (int) $numFiles, + \count($multistatusResults), + __METHOD__ + . " Expected result to contain '" + . (int) $numFiles + . "' files/entries, but got '" + . \count($multistatusResults) + . "' files/entries." + ); + } + + /** + * @Then the propfind/search result should contain any :expectedNumber of these files/entries: + * + * @param integer $expectedNumber + * @param TableNode $expectedFiles + * + * @return void + * @throws Exception + */ + public function theSearchResultShouldContainAnyOfTheseEntries( + int $expectedNumber, + TableNode $expectedFiles + ):void { + $this->theSearchResultOfUserShouldContainAnyOfTheseEntries( + $this->getCurrentUser(), + $expectedNumber, + $expectedFiles + ); + } + + /** + * @Then the propfind/search result of user :user should contain any :expectedNumber of these files/entries: + * + * @param string $user + * @param integer $expectedNumber + * @param TableNode $expectedFiles + * + * @return void + * @throws Exception + */ + public function theSearchResultOfUserShouldContainAnyOfTheseEntries( + string $user, + int $expectedNumber, + TableNode $expectedFiles + ):void { + $user = $this->getActualUsername($user); + $this->verifyTableNodeColumnsCount($expectedFiles, 1); + $this->propfindResultShouldContainNumEntries($expectedNumber); + $elementRows = $expectedFiles->getColumn(0); + // Remove any "/" from the front (or back) of the expected values passed + // into the step. findEntryFromPropfindResponse returns entries without + // any leading (or trailing) slash + $expectedEntries = \array_map( + function ($value) { + return \trim($value, "/"); + }, + $elementRows + ); + $resultEntries = $this->findEntryFromPropfindResponse(null, $user, "REPORT"); + foreach ($resultEntries as $resultEntry) { + Assert::assertContains($resultEntry, $expectedEntries); + } + } + + /** + * @When user :arg1 lists the resources in :path with depth :depth using the WebDAV API + * + * @param string $user + * @param string $path + * @param string $depth + * + * @return void + * @throws Exception + */ + public function userListsTheResourcesInPathWithDepthUsingTheWebdavApi(string $user, string $path, string $depth):void { + $response = $this->listFolder( + $user, + $path, + $depth + ); + $this->setResponse($response); + $this->setResponseXml(HttpRequestHelper::parseResponseAsXml($this->response)); + } + + /** + * @Then the last DAV response for user :user should contain these nodes/elements + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theLastDavResponseShouldContainTheseNodes(string $user, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["name"]); + foreach ($table->getHash() as $row) { + $path = $this->substituteInLineCodes($row['name']); + $res = $this->findEntryFromPropfindResponse($path, $user); + Assert::assertNotFalse($res, "expected $path to be in DAV response but was not found"); + } + } + + /** + * @Then the last DAV response for user :user should not contain these nodes/elements + * + * @param string $user + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theLastDavResponseShouldNotContainTheseNodes(string $user, TableNode $table):void { + $this->verifyTableNodeColumns($table, ["name"]); + foreach ($table->getHash() as $row) { + $path = $this->substituteInLineCodes($row['name']); + $res = $this->findEntryFromPropfindResponse($path, $user); + Assert::assertFalse($res, "expected $path to not be in DAV response but was found"); + } + } + + /** + * @Then the last public link DAV response should contain these nodes/elements + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theLastPublicDavResponseShouldContainTheseNodes(TableNode $table):void { + $user = $this->getLastPublicShareToken(); + $this->verifyTableNodeColumns($table, ["name"]); + $type = $this->usingOldDavPath ? "public-files" : "public-files-new"; + foreach ($table->getHash() as $row) { + $path = $this->substituteInLineCodes($row['name']); + $res = $this->findEntryFromPropfindResponse($path, $user, null, $type); + Assert::assertNotFalse($res, "expected $path to be in DAV response but was not found"); + } + } + + /** + * @Then the last public link DAV response should not contain these nodes/elements + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theLastPublicDavResponseShouldNotContainTheseNodes(TableNode $table):void { + $user = $this->getLastPublicShareToken(); + $this->verifyTableNodeColumns($table, ["name"]); + $type = $this->usingOldDavPath ? "public-files" : "public-files-new"; + foreach ($table->getHash() as $row) { + $path = $this->substituteInLineCodes($row['name']); + $res = $this->findEntryFromPropfindResponse($path, $user, null, $type); + Assert::assertFalse($res, "expected $path to not be in DAV response but was found"); + } + } + + /** + * @When the public lists the resources in the last created public link with depth :depth using the WebDAV API + * + * @param string $depth + * + * @return void + * @throws Exception + */ + public function thePublicListsTheResourcesInTheLastCreatedPublicLinkWithDepthUsingTheWebdavApi(string $depth):void { + $user = $this->getLastPublicShareToken(); + $response = $this->listFolder( + $user, + '/', + $depth, + null, + $this->usingOldDavPath ? "public-files" : "public-files-new" + ); + $this->setResponse($response); + $this->setResponseXml(HttpRequestHelper::parseResponseAsXml($this->response)); + } + + /** + * @param string|null $user + * + * @return array + */ + public function findEntryFromReportResponse(?string $user):array { + $responseXmlObj = $this->getResponseXmlObject(); + $responseResources = []; + $hrefs = $responseXmlObj->xpath('//d:href'); + foreach ($hrefs as $href) { + $hrefParts = \explode("/", (string)$href[0]); + if (\in_array($user, $hrefParts)) { + $entry = \urldecode(\end($hrefParts)); + \array_push($responseResources, $entry); + } else { + throw new Error("Expected user: $hrefParts[5] but found: $user"); + } + } + return $responseResources; + } + + /** + * parses a PROPFIND response from $this->response into xml + * and returns found search results if found else returns false + * + * @param string|null $user + * + * @return int + */ + public function getNumberOfEntriesInPropfindResponse( + ?string $user = null + ):int { + $multistatusResults = $this->getMultistatusResultFromPropfindResult($user); + return \count($multistatusResults); + } + + /** + * parses a PROPFIND response from $this->response + * and returns multistatus data from the response + * + * @param string|null $user + * + * @return array + */ + public function getMultistatusResultFromPropfindResult( + ?string $user = null + ):array { + //if we are using that step the second time in a scenario e.g. 'But ... should not' + //then don't parse the result again, because the result in a ResponseInterface + if (empty($this->responseXml)) { + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->response) + ); + } + Assert::assertNotEmpty($this->responseXml, __METHOD__ . ' Response is empty'); + if ($user === null) { + $user = $this->getCurrentUser(); + } + + Assert::assertIsArray( + $this->responseXml, + __METHOD__ . " responseXml for user $user is not an array" + ); + Assert::assertArrayHasKey( + "value", + $this->responseXml, + __METHOD__ . " responseXml for user $user does not have key 'value'" + ); + $multistatus = $this->responseXml["value"]; + if ($multistatus == null) { + $multistatus = []; + } + return $multistatus; + } + + /** + * Escapes the given string for + * 1. Space --> %20 + * 2. Opening Small Bracket --> %28 + * 3. Closing Small Bracket --> %29 + * + * @param string $path - File path to parse + * + * @return string + */ + public function escapePath(string $path): string { + return \str_replace([" ", "(", ")"], ["%20", "%28", "%29"], $path); + } + + /** + * parses a PROPFIND response from $this->response into xml + * and returns found search results if found else returns false + * + * @param string|null $entryNameToSearch + * @param string|null $user + * @param string|null $method + * @param string $type + * @param string $folderPath + * + * @return string|array|boolean + * + * string if $entryNameToSearch is given and is found + * array if $entryNameToSearch is not given + * boolean false if $entryNameToSearch is given and is not found + * + * @throws GuzzleException + */ + public function findEntryFromPropfindResponse( + ?string $entryNameToSearch = null, + ?string $user = null, + ?string $method = null, + string $type = "files", + string $folderPath = '' + ) { + $trimmedEntryNameToSearch = ''; + // trim any leading "/" passed by the caller, we can just match the "raw" name + if ($entryNameToSearch != null) { + $trimmedEntryNameToSearch = \trim($entryNameToSearch, "/"); + } + // url encode for spaces and brackets that may appear in the filePath + $folderPath = $this->escapePath($folderPath); + // topWebDavPath should be something like /remote.php/webdav/ or + // /remote.php/dav/files/alice/ + $topWebDavPath = "/" . $this->getFullDavFilesPath($user) . "/" . $folderPath; + switch ($type) { + case "files": + break; + case "public-files": + case "public-files-old": + case "public-files-new": + $topWebDavPath = "/" . $this->getPublicLinkDavPath($user, $type) . "/"; + break; + default: + throw new Exception("error"); + } + $multistatusResults = $this->getMultistatusResultFromPropfindResult($user); + $results = []; + if ($multistatusResults !== null) { + foreach ($multistatusResults as $multistatusResult) { + $entryPath = $multistatusResult['value'][0]['value']; + if (OcisHelper::isTestingOnOcis() && $method === "REPORT") { + if ($entryNameToSearch !== null && str_ends_with($entryPath, $entryNameToSearch)) { + return $multistatusResult; + } else { + $spaceId = (WebDavHelper::$SPACE_ID_FROM_OCIS) ? WebDavHelper::$SPACE_ID_FROM_OCIS : WebDavHelper::getPersonalSpaceIdForUser( + $this->getBaseUrl(), + $user, + $this->getPasswordForUser($user), + $this->getStepLineRef() + ); + $topWebDavPath = "/remote.php/dav/spaces/" . $spaceId . "/" . $folderPath; + } + } + $entryName = \str_replace($topWebDavPath, "", $entryPath); + $entryName = \rawurldecode($entryName); + $entryName = \trim($entryName, "/"); + if ($trimmedEntryNameToSearch === $entryName) { + return $multistatusResult; + } + \array_push($results, $entryName); + } + } + if ($entryNameToSearch === null) { + return $results; + } + return false; + } + + /** + * Prevent creating two uploads and/or deletes with the same "stime" + * That is based on seconds in some implementations. + * This prevents duplication of etags or other problems with + * trashbin/versions save/restore. + * + * Set env var UPLOAD_DELETE_WAIT_TIME to 1 to activate a 1-second pause. + * By default, there is no pause. That allows testing of implementations + * which should be able to cope with multiple upload/delete actions in the + * same second. + * + * @return void + */ + public function pauseUploadDelete():void { + $time = \time(); + $uploadWaitTime = \getenv("UPLOAD_DELETE_WAIT_TIME"); + + $uploadWaitTime = $uploadWaitTime ? (int)$uploadWaitTime : 0; + + if (($this->lastUploadDeleteTime !== null) + && ($uploadWaitTime > 0) + && (($time - $this->lastUploadDeleteTime) < $uploadWaitTime) + ) { + \sleep($uploadWaitTime); + } + } + + /** + * reset settings if they were set in the scenario + * + * @AfterScenario + * + * @return void + * @throws Exception + */ + public function resetPreviousSettingsAfterScenario():void { + if ($this->previousAsyncSetting === "") { + SetupHelper::runOcc( + ['config:system:delete', 'dav.enable.async'], + $this->getStepLineRef() + ); + } elseif ($this->previousAsyncSetting !== null) { + SetupHelper::runOcc( + [ + 'config:system:set', + 'dav.enable.async', + '--type', + 'boolean', + '--value', + $this->previousAsyncSetting + ], + $this->getStepLineRef() + ); + } + if ($this->previousDavSlowdownSetting === "") { + SetupHelper::runOcc( + ['config:system:delete', 'dav.slowdown'], + $this->getStepLineRef() + ); + } elseif ($this->previousDavSlowdownSetting !== null) { + SetupHelper::runOcc( + [ + 'config:system:set', + 'dav.slowdown', + '--value', + $this->previousDavSlowdownSetting + ], + $this->getStepLineRef() + ); + } + } + + /** + * @Given /^the administrator has (enabled|disabled) the file version storage feature/ + * + * @param string $enabledOrDisabled + * + * @return void + * @throws Exception + */ + public function theAdministratorHasEnabledTheFileVersionStorage(string $enabledOrDisabled): void { + $switch = ($enabledOrDisabled !== "disabled"); + if ($switch) { + $value = 'true'; + } else { + $value = 'false'; + } + $this->runOcc( + [ + 'config:system:set', + 'file_storage.save_version_author', + '--type', + 'boolean', + '--value', + $value] + ); + } + + /** + * @Then the author of the created version with index :index should be :expectedUsername + * + * @param string $index + * @param string $expectedUsername + * + * @return void + * @throws Exception + */ + public function theAuthorOfEditedVersionFile(string $index, string $expectedUsername): void { + $expectedUserDisplayName = $this->getUserDisplayName($expectedUsername); + $resXml = $this->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->getResponse(), + __METHOD__ + ); + $this->setResponseXmlObject($resXml); + } + + // the username should be in oc:meta-version-edited-by + $xmlPart = $resXml->xpath("//oc:meta-version-edited-by//text()"); + if (!isset($xmlPart[$index - 1])) { + Assert::fail( + 'could not find version with index "' . $index . '" for oc:meta-version-edited-by property in response to user "' . $this->responseUser . '"' + ); + } + $actualUser = $xmlPart[$index - 1][0]; + Assert::assertEquals( + $expectedUsername, + $actualUser, + "Expected user of version with index $index in response to user '$this->responseUser' was '$expectedUsername', but got '$actualUser'" + ); + + // the user's display name should be in oc:meta-version-edited-by-name + $xmlPart = $resXml->xpath("//oc:meta-version-edited-by-name//text()"); + if (!isset($xmlPart[$index - 1])) { + Assert::fail( + 'could not find version with index "' . $index . '" for oc:meta-version-edited-by-name property in response to user "' . $this->responseUser . '"' + ); + } + $actualUserDisplayName = $xmlPart[$index - 1][0]; + Assert::assertEquals( + $expectedUserDisplayName, + $actualUserDisplayName, + "Expected display name of version with index $index in response to user '$this->responseUser' was '$expectedUsername', but got '$actualUser'" + ); + } +} diff --git a/tests/acceptance/features/bootstrap/WebDavLockingContext.php b/tests/acceptance/features/bootstrap/WebDavLockingContext.php new file mode 100644 index 00000000000..dc9b92e2f93 --- /dev/null +++ b/tests/acceptance/features/bootstrap/WebDavLockingContext.php @@ -0,0 +1,700 @@ + + * @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\GuzzleException; +use PHPUnit\Framework\Assert; +use TestHelpers\HttpRequestHelper; +use TestHelpers\OcsApiHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * context containing API steps needed for the locking mechanism of webdav + */ +class WebDavLockingContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * + * @var PublicWebDavContext + */ + private $publicWebDavContext; + + /** + * + * @var string[][] + */ + private $tokenOfLastLock = []; + + /** + * + * @param string $user + * @param string $file + * @param TableNode $properties table with no heading with | property | value | + * @param boolean $public if the file is in a public share or not + * @param boolean $expectToSucceed + * + * @return void + */ + private function lockFile( + $user, + $file, + TableNode $properties, + $public = false, + $expectToSucceed = true + ) { + $user = $this->featureContext->getActualUsername($user); + $baseUrl = $this->featureContext->getBaseUrl(); + if ($public === true) { + $type = "public-files"; + $password = null; + } else { + $type = "files"; + $password = $this->featureContext->getPasswordForUser($user); + } + $body + = "" . + " "; + $headers = []; + $this->featureContext->verifyTableNodeRows($properties, [], ['lockscope', 'depth', 'timeout']); + $propertiesRows = $properties->getRowsHash(); + foreach ($propertiesRows as $property => $value) { + if ($property === "depth" || $property === "timeout") { + //properties that are set in the header not in the xml + $headers[$property] = $value; + } else { + $body .= ""; + } + } + + $body .= ""; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "LOCK", + $file, + $headers, + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion(), + $type + ); + + $this->featureContext->setResponse($response); + $responseXml = $this->featureContext->getResponseXml(null, __METHOD__); + $this->featureContext->setResponseXmlObject($responseXml); + $xmlPart = $responseXml->xpath("//d:locktoken/d:href"); + if (isset($xmlPart[0])) { + $this->tokenOfLastLock[$user][$file] = (string) $xmlPart[0]; + } else { + if ($expectToSucceed === true) { + Assert::fail("could not find lock token after trying to lock '$file'"); + } + } + } + + /** + * @When user :user locks file/folder :file using the WebDAV API setting the following properties + * + * @param string $user + * @param string $file + * @param TableNode $properties table with no heading with | property | value | + * + * @return void + */ + public function lockFileUsingWebDavAPI($user, $file, TableNode $properties) { + $this->lockFile($user, $file, $properties, false, false); + } + + /** + * @Given user :user has locked file/folder :file setting the following properties + * + * @param string $user + * @param string $file + * @param TableNode $properties table with no heading with | property | value | + * + * @return void + */ + public function userHasLockedFile($user, $file, TableNode $properties) { + $this->lockFile($user, $file, $properties, false, true); + } + + /** + * @Given the public has locked the last public link shared file/folder setting the following properties + * + * @param TableNode $properties + * + * @return void + */ + public function publicHasLockedLastSharedFile(TableNode $properties) { + $this->lockFile( + $this->featureContext->getLastPublicShareToken(), + "/", + $properties, + true + ); + } + + /** + * @When the public locks the last public link shared file/folder using the WebDAV API setting the following properties + * + * @param TableNode $properties + * + * @return void + */ + public function publicLocksLastSharedFile(TableNode $properties) { + $this->lockFile( + $this->featureContext->getLastPublicShareToken(), + "/", + $properties, + true, + false + ); + } + + /** + * @Given the public has locked :file in the last public link shared folder setting the following properties + * + * @param string $file + * @param TableNode $properties + * + * @return void + */ + public function publicHasLockedFileLastSharedFolder( + $file, + TableNode $properties + ) { + $this->lockFile( + $this->featureContext->getLastPublicShareToken(), + $file, + $properties, + true + ); + } + + /** + * @When /^the public locks "([^"]*)" in the last public link shared folder using the (old|new) public WebDAV API setting the following properties$/ + * + * @param string $file + * @param string $publicWebDAVAPIVersion + * @param TableNode $properties + * + * @return void + */ + public function publicLocksFileLastSharedFolder( + $file, + $publicWebDAVAPIVersion, + TableNode $properties + ) { + $this->lockFile( + $this->featureContext->getLastPublicShareToken(), + $file, + $properties, + true, + false + ); + } + + /** + * @When user :user unlocks the last created lock of file/folder :file using the WebDAV API + * + * @param string $user + * @param string $file + * + * @return void + */ + public function unlockLastLockUsingWebDavAPI($user, $file) { + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $file, + $user, + $file + ); + } + + /** + * @When user :user unlocks file/folder :itemToUnlock with the last created lock of file/folder :itemToUseLockOf using the WebDAV API + * + * @param string $user + * @param string $itemToUnlock + * @param string $itemToUseLockOf + * + * @return void + */ + public function unlockItemWithLastLockOfOtherItemUsingWebDavAPI( + $user, + $itemToUnlock, + $itemToUseLockOf + ) { + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $itemToUnlock, + $user, + $itemToUseLockOf + ); + } + + /** + * @When user :user unlocks file/folder :itemToUnlock with the last created public lock of file/folder :itemToUseLockOf using the WebDAV API + * + * @param string $user + * @param string $itemToUnlock + * @param string $itemToUseLockOf + * + * @return void + */ + public function unlockItemWithLastPublicLockOfOtherItemUsingWebDavAPI( + $user, + $itemToUnlock, + $itemToUseLockOf + ) { + $lockOwner = $this->featureContext->getLastPublicShareToken(); + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $itemToUnlock, + $lockOwner, + $itemToUseLockOf + ); + } + + /** + * + * @param string $user + * @param string $itemToUnlock + * + * @return int|void + * + * @throws Exception|GuzzleException + */ + private function countLockOfResources( + string $user, + string $itemToUnlock + ) { + $user = $this->featureContext->getActualUsername($user); + $baseUrl = $this->featureContext->getBaseUrl(); + $password = $this->featureContext->getPasswordForUser($user); + $body + = "" . + " " . + "" . + ""; + $response = WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "PROPFIND", + $itemToUnlock, + null, + $this->featureContext->getStepLineRef(), + $body, + $this->featureContext->getDavPathVersion() + ); + $responseXml = $this->featureContext->getResponseXml($response, __METHOD__); + $xmlPart = $responseXml->xpath("//d:response//d:lockdiscovery/d:activelock"); + if (\is_array($xmlPart)) { + return \count($xmlPart); + } else { + throw new Exception("xmlPart for 'd:activelock' was expected to be array but found: $xmlPart"); + } + } + + /** + * @Given user :user has unlocked file/folder :itemToUnlock with the last created lock of file/folder :itemToUseLockOf of user :lockOwner using the WebDAV API + * + * @param string $user + * @param string $itemToUnlock + * @param string $lockOwner + * @param string $itemToUseLockOf + * @param boolean $public + * + * @return void + * @throws Exception|GuzzleException + */ + public function hasUnlockItemWithTheLastCreatedLock( + $user, + $itemToUnlock, + $lockOwner, + $itemToUseLockOf, + $public = false + ) { + $lockCount = $this->countLockOfResources($user, $itemToUnlock); + + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $itemToUnlock, + $lockOwner, + $itemToUseLockOf, + $public + ); + $this->featureContext->theHTTPStatusCodeShouldBe(204); + + $this->numberOfLockShouldBeReported($lockCount - 1, $itemToUnlock, $user); + } + + /** + * @When user :user unlocks file/folder :itemToUnlock with the last created lock of file/folder :itemToUseLockOf of user :lockOwner using the WebDAV API + * + * @param string $user + * @param string $itemToUnlock + * @param string $lockOwner + * @param string $itemToUseLockOf + * @param boolean $public + * + * @return void + */ + public function unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + string $user, + string $itemToUnlock, + string $lockOwner, + string $itemToUseLockOf, + bool $public = false + ) { + $user = $this->featureContext->getActualUsername($user); + $lockOwner = $this->featureContext->getActualUsername($lockOwner); + if ($public === true) { + $type = "public-files"; + $password = null; + } else { + $type = "files"; + $password = $this->featureContext->getPasswordForUser($user); + } + $baseUrl = $this->featureContext->getBaseUrl(); + if (!isset($this->tokenOfLastLock[$lockOwner][$itemToUseLockOf])) { + Assert::fail( + "could not find saved token of '$itemToUseLockOf' " . + "owned by user '$lockOwner'" + ); + } + $headers = [ + "Lock-Token" => $this->tokenOfLastLock[$lockOwner][$itemToUseLockOf] + ]; + $this->featureContext->setResponse( + WebDavHelper::makeDavRequest( + $baseUrl, + $user, + $password, + "UNLOCK", + $itemToUnlock, + $headers, + $this->featureContext->getStepLineRef(), + null, + $this->featureContext->getDavPathVersion(), + $type + ) + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @When the public unlocks file/folder :itemToUnlock with the last created lock of file/folder :itemToUseLockOf of user :lockOwner using the WebDAV API + * + * @param string $itemToUnlock + * @param string $lockOwner + * @param string $itemToUseLockOf + * + * @return void + */ + public function unlockItemAsPublicWithLastLockOfUserAndItemUsingWebDavAPI( + $itemToUnlock, + $lockOwner, + $itemToUseLockOf + ) { + $user = $this->featureContext->getLastPublicShareToken(); + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $itemToUnlock, + $lockOwner, + $itemToUseLockOf, + true + ); + } + + /** + * @When the public unlocks file/folder :itemToUnlock using the WebDAV API + * + * @param string $itemToUnlock + * + * @return void + */ + public function unlockItemAsPublicUsingWebDavAPI($itemToUnlock) { + $user = $this->featureContext->getLastPublicShareToken(); + $this->unlockItemWithLastLockOfUserAndItemUsingWebDavAPI( + $user, + $itemToUnlock, + $user, + $itemToUnlock, + true + ); + } + + /** + * @When /^user "([^"]*)" moves (?:file|folder|entry) "([^"]*)" to "([^"]*)" sending the locktoken of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileSource + * @param string $fileDestination + * @param string $itemToUseLockOf + * + * @return void + */ + public function moveItemSendingLockToken( + $user, + $fileSource, + $fileDestination, + $itemToUseLockOf + ) { + $this->moveItemSendingLockTokenOfUser( + $user, + $fileSource, + $fileDestination, + $itemToUseLockOf, + $user + ); + } + + /** + * @When /^user "([^"]*)" moves (?:file|folder|entry) "([^"]*)" to "([^"]*)" sending the locktoken of (?:file|folder|entry) "([^"]*)" of user "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $fileSource + * @param string $fileDestination + * @param string $itemToUseLockOf + * @param string $lockOwner + * + * @return void + */ + public function moveItemSendingLockTokenOfUser( + $user, + $fileSource, + $fileDestination, + $itemToUseLockOf, + $lockOwner + ) { + $user = $this->featureContext->getActualUsername($user); + $lockOwner = $this->featureContext->getActualUsername($lockOwner); + $destination = $this->featureContext->destinationHeaderValue( + $user, + $fileDestination + ); + $token = $this->tokenOfLastLock[$lockOwner][$itemToUseLockOf]; + $headers = [ + "Destination" => $destination, + "If" => "(<$token>)" + ]; + try { + $response = $this->featureContext->makeDavRequest( + $user, + "MOVE", + $fileSource, + $headers + ); + $this->featureContext->setResponse($response); + } catch (ConnectException $e) { + } + } + + /** + * @When /^user "([^"]*)" uploads file with content "([^"]*)" to "([^"]*)" sending the locktoken of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $content + * @param string $destination + * @param string $itemToUseLockOf + * + * @return void + */ + public function userUploadsAFileWithContentTo( + $user, + $content, + $destination, + $itemToUseLockOf + ) { + $user = $this->featureContext->getActualUsername($user); + $token = $this->tokenOfLastLock[$user][$itemToUseLockOf]; + $this->featureContext->pauseUploadDelete(); + $response = $this->featureContext->makeDavRequest( + $user, + "PUT", + $destination, + ["If" => "(<$token>)"], + $content + ); + $this->featureContext->setResponse($response); + $this->featureContext->setLastUploadDeleteTime(\time()); + } + + /** + * @When /^the public uploads file "([^"]*)" with content "([^"]*)" sending the locktoken of file "([^"]*)" of user "([^"]*)" using the (old|new) public WebDAV API$/ + * + * @param string $filename + * @param string $content + * @param string $itemToUseLockOf + * @param string $lockOwner + * @param string $publicWebDAVAPIVersion + * + * @return void + * + */ + public function publicUploadFileSendingLockTokenOfUser( + $filename, + $content, + $itemToUseLockOf, + $lockOwner, + $publicWebDAVAPIVersion + ) { + $lockOwner = $this->featureContext->getActualUsername($lockOwner); + $headers = [ + "If" => "(<" . $this->tokenOfLastLock[$lockOwner][$itemToUseLockOf] . ">)" + ]; + $this->publicWebDavContext->publicUploadContent( + $filename, + '', + $content, + false, + $headers, + $publicWebDAVAPIVersion + ); + } + + /** + * @When /^the public uploads file "([^"]*)" with content "([^"]*)" sending the locktoken of "([^"]*)" of the public using the (old|new) public WebDAV API$/ + * + * @param string $filename + * @param string $content + * @param string $itemToUseLockOf + * @param string $publicWebDAVAPIVersion + * + * @return void + */ + public function publicUploadFileSendingLockTokenOfPublic( + $filename, + $content, + $itemToUseLockOf, + $publicWebDAVAPIVersion + ) { + $lockOwner = $this->featureContext->getLastPublicShareToken(); + $this->publicUploadFileSendingLockTokenOfUser( + $filename, + $content, + $itemToUseLockOf, + $lockOwner, + $publicWebDAVAPIVersion + ); + } + + /** + * @Then :count locks should be reported for file/folder :file of user :user by the WebDAV API + * + * @param int $count + * @param string $file + * @param string $user + * + * @return void + * @throws GuzzleException + */ + public function numberOfLockShouldBeReported($count, $file, $user) { + $lockCount = $this->countLockOfResources($user, $file); + Assert::assertEquals( + $count, + $lockCount, + "Expected $count lock(s) for '$file' but found '$lockCount'" + ); + } + + /** + * @Then group :expectedGroup should exist as a lock breaker group + * + * @param string $expectedGroup + * + * @return void + * + * @throws Exception + */ + public function groupShouldExistAsLockBreakerGroups($expectedGroup) { + $baseUrl = $this->featureContext->getBaseUrl(); + $admin = $this->featureContext->getAdminUsername(); + $password = $this->featureContext->getAdminPassword(); + $ocsApiVersion = $this->featureContext->getOcsApiVersion(); + + $response = OcsApiHelper::sendRequest( + $baseUrl, + $admin, + $password, + 'GET', + "/apps/testing/api/v1/app/core/lock-breaker-groups", + (string) $ocsApiVersion + ); + + $responseXml = HttpRequestHelper::getResponseXml($response, __METHOD__)->data->element; + $lockbreakergroup = trim(\json_decode(\json_encode($responseXml), true)['value'], '\'[]"'); + $actualgroup = explode("\",\"", $lockbreakergroup); + if (!\in_array($expectedGroup, $actualgroup)) { + Assert::fail("could not find group '$expectedGroup' in lock breakers group"); + } + } + + /** + * @Then following groups should exist as lock breaker groups + * + * @param TableNode $table + * + * @return void + * + * @throws Exception + */ + public function followingGroupShouldExistAsLockBreakerGroups(TableNode $table) { + $this->featureContext->verifyTableNodeColumns($table, ["groups"]); + $paths = $table->getHash(); + + foreach ($paths as $group) { + $this->groupShouldExistAsLockBreakerGroups($group["groups"]); + } + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope) { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + $this->publicWebDavContext = $environment->getContext('PublicWebDavContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php new file mode 100644 index 00000000000..62275ef27b8 --- /dev/null +++ b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php @@ -0,0 +1,1396 @@ + + * @copyright Copyright (c) 2019, ownCloud GmbH + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, + * as published by the Free Software Foundation; + * either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use TestHelpers\Asserts\WebDav as WebDavTest; +use TestHelpers\HttpRequestHelper; +use TestHelpers\WebDavHelper; + +require_once 'bootstrap.php'; + +/** + * Steps that relate to managing file/folder properties via WebDav + */ +class WebDavPropertiesContext implements Context { + /** + * + * @var FeatureContext + */ + private $featureContext; + + /** + * @var array map with user as key and another map as value, + * which has path as key and etag as value + */ + private $storedETAG = null; + + /** + * @When /^user "([^"]*)" gets the properties of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $path + * + * @return void + * @throws Exception + */ + public function userGetsThePropertiesOfFolder( + string $user, + string $path + ):void { + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $path, + '0' + ) + ); + } + + /** + * @When /^user "([^"]*)" gets the properties of (?:file|folder|entry) "([^"]*)" with depth (\d+) using the WebDAV API$/ + * + * @param string $user + * @param string $path + * @param string $depth + * + * @return void + * @throws Exception + */ + public function userGetsThePropertiesOfFolderWithDepth( + string $user, + string $path, + string $depth + ):void { + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $path, + $depth + ) + ); + } + + /** + * @When /^user "([^"]*)" gets the following properties of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $path + * @param TableNode|null $propertiesTable + * + * @return void + * @throws Exception + */ + public function userGetsPropertiesOfFolder( + string $user, + string $path, + TableNode $propertiesTable + ):void { + $user = $this->featureContext->getActualUsername($user); + $properties = null; + $this->featureContext->verifyTableNodeColumns($propertiesTable, ["propertyName"]); + $this->featureContext->verifyTableNodeColumnsCount($propertiesTable, 1); + if ($propertiesTable instanceof TableNode) { + foreach ($propertiesTable->getColumnsHash() as $row) { + $properties[] = $row["propertyName"]; + } + } + $depth = "1"; + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $path, + $depth, + $properties + ) + ); + $this->featureContext->pushToLastStatusCodesArrays(); + } + + /** + * @param string $user + * @param string $path + * @param TableNode $propertiesTable + * @param string $depth + * + * @return void + * @throws Exception + */ + public function getFollowingCommentPropertiesOfFileUsingWebDAVPropfindApi( + string $user, + string $path, + TableNode $propertiesTable, + string $depth = "1" + ):void { + $properties = null; + $this->featureContext->verifyTableNodeColumns($propertiesTable, ["propertyName"]); + $this->featureContext->verifyTableNodeColumnsCount($propertiesTable, 1); + if ($propertiesTable instanceof TableNode) { + foreach ($propertiesTable->getColumnsHash() as $row) { + $properties[] = $row["propertyName"]; + } + } + + $user = $this->featureContext->getActualUsername($user); + $fileId = $this->featureContext->getFileIdForPath($user, $path); + $commentsPath = "/comments/files/$fileId/"; + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $commentsPath, + $depth, + $properties, + "comments" + ) + ); + } + + /** + * @When user :user gets the following comment properties of file :path using the WebDAV PROPFIND API + * + * @param string $user + * @param string $path + * @param TableNode $propertiesTable + * + * @return void + * @throws Exception + */ + public function userGetsFollowingCommentPropertiesOfFileUsingWebDAVPropfindApi(string $user, string $path, TableNode $propertiesTable) { + $this->getFollowingCommentPropertiesOfFileUsingWebDAVPropfindApi( + $user, + $path, + $propertiesTable + ); + } + + /** + * @When the user gets the following comment properties of file :arg1 using the WebDAV PROPFIND API + * + * @param string $path + * @param TableNode $propertiesTable + * + * @return void + * @throws Exception + */ + public function theUserGetsFollowingCommentPropertiesOfFileUsingWebDAVPropfindApi(string $path, TableNode $propertiesTable) { + $this->getFollowingCommentPropertiesOfFileUsingWebDAVPropfindApi( + $this->featureContext->getCurrentUser(), + $path, + $propertiesTable + ); + } + + /** + * @When /^the user gets the following properties of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/ + * + * @param string $path + * @param TableNode|null $propertiesTable + * + * @return void + * @throws Exception + */ + public function theUserGetsPropertiesOfFolder(string $path, TableNode $propertiesTable) { + $this->userGetsPropertiesOfFolder( + $this->featureContext->getCurrentUser(), + $path, + $propertiesTable + ); + } + + /** + * @Given /^user "([^"]*)" has set the following properties of (?:file|folder|entry) "([^"]*)" using the WebDav API$/ + * + * if no namespace prefix is provided before property, default `oc:` prefix is set for added props + * only and everything rest on xml is set to prefix `d:` + * + * @param string $username + * @param string $path + * @param TableNode|null $propertiesTable with following columns with column header as: + * property: name of prop to be set + * value: value of prop to be set + * + * @return void + * @throws Exception + */ + public function userHasSetFollowingPropertiesUsingProppatch(string $username, string $path, TableNode $propertiesTable) { + $username = $this->featureContext->getActualUsername($username); + $this->featureContext->verifyTableNodeColumns($propertiesTable, ['propertyName', 'propertyValue']); + $properties = $propertiesTable->getColumnsHash(); + $this->featureContext->setResponse( + WebDavHelper::proppatchWithMultipleProps( + $this->featureContext->getBaseUrl(), + $username, + $this->featureContext->getPasswordForUser($username), + $path, + $properties, + $this->featureContext->getStepLineRef(), + $this->featureContext->getDavPathVersion() + ) + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @When user :user gets a custom property :propertyName with namespace :namespace of file :path + * + * @param string $user + * @param string $propertyName + * @param string $namespace namespace in form of "x1='http://whatever.org/ns'" + * @param string $path + * + * @return void + * @throws Exception + */ + public function userGetsPropertiesOfFile( + string $user, + string $propertyName, + string $namespace, + string $path + ):void { + $user = $this->featureContext->getActualUsername($user); + $properties = [ + $namespace => $propertyName + ]; + $this->featureContext->setResponse( + WebDavHelper::propfind( + $this->featureContext->getBaseUrl(), + $this->featureContext->getActualUsername($user), + $this->featureContext->getUserPassword($user), + $path, + $properties, + $this->featureContext->getStepLineRef(), + "0", + "files", + $this->featureContext->getDavPathVersion() + ) + ); + } + + /** + * @When /^the public gets the following properties of (?:file|folder|entry) "([^"]*)" in the last created public link using the WebDAV API$/ + * + * @param string $path + * @param TableNode $propertiesTable + * + * @return void + * @throws Exception + */ + public function publicGetsThePropertiesOfFolder(string $path, TableNode $propertiesTable):void { + $user = $this->featureContext->getLastPublicShareToken(); + $properties = null; + if ($propertiesTable instanceof TableNode) { + foreach ($propertiesTable->getRows() as $row) { + $properties[] = $row[0]; + } + } + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $path, + '0', + $properties, + $this->featureContext->getDavPathVersion() === 1 ? "public-files" : "public-files-new" + ) + ); + } + + /** + * @param string $user user id who sets the property + * @param string $propertyName name of property in Clark notation + * @param string $namespace namespace in form of "x1='http://whatever.org/ns'" + * @param string $path path on which to set properties to + * @param string $propertyValue property value + * + * @return void + * @throws Exception + */ + public function setPropertyWithNamespaceOfResource( + string $user, + string $propertyName, + string $namespace, + string $path, + string $propertyValue + ):void { + $user = $this->featureContext->getActualUsername($user); + $response = WebDavHelper::proppatch( + $this->featureContext->getBaseUrl(), + $this->featureContext->getActualUsername($user), + $this->featureContext->getUserPassword($user), + $path, + $propertyName, + $propertyValue, + $this->featureContext->getStepLineRef(), + $namespace, + $this->featureContext->getDavPathVersion() + ); + $this->featureContext->setResponse($response); + } + + /** + * @When /^user "([^"]*)" sets property "([^"]*)" with namespace "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $user user id who sets the property + * @param string $propertyName name of property in Clark notation + * @param string $namespace namespace in form of "x1='http://whatever.org/ns'" + * @param string $path path on which to set properties to + * @param string $propertyValue property value + * + * @return void + * @throws Exception + */ + public function userSetsPropertyWithNamespaceOfEntryTo( + string $user, + string $propertyName, + string $namespace, + string $path, + string $propertyValue + ):void { + $this->setPropertyWithNamespaceOfResource( + $user, + $propertyName, + $namespace, + $path, + $propertyValue + ); + } + + /** + * @Given /^user "([^"]*)" has set property "([^"]*)" with namespace "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/ + * + * @param string $user user id who sets the property + * @param string $propertyName name of property in Clark notation + * @param string $namespace namespace in form of "x1='http://whatever.org/ns'" + * @param string $path path on which to set properties to + * @param string $propertyValue property value + * + * @return void + * @throws Exception + */ + public function userHasSetPropertyWithNamespaceOfEntryTo( + string $user, + string $propertyName, + string $namespace, + string $path, + string $propertyValue + ):void { + $this->setPropertyWithNamespaceOfResource( + $user, + $propertyName, + $namespace, + $path, + $propertyValue + ); + $this->featureContext->theHTTPStatusCodeShouldBeSuccess(); + } + + /** + * @Then /^the response should contain a custom "([^"]*)" property with namespace "([^"]*)" and value "([^"]*)"$/ + * + * @param string $propertyName + * @param string $namespaceString + * @param string $propertyValue + * + * @return void + * @throws Exception + */ + public function theResponseShouldContainACustomPropertyWithValue( + string $propertyName, + string $namespaceString, + string $propertyValue + ):void { + $this->featureContext->setResponseXmlObject( + HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ) + ); + $responseXmlObject = $this->featureContext->getResponseXmlObject(); + //calculate the namespace prefix and namespace + $matches = []; + \preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches); + $nameSpace = $matches[2]; + $nameSpacePrefix = $matches[1]; + $responseXmlObject->registerXPathNamespace( + $nameSpacePrefix, + $nameSpace + ); + $xmlPart = $responseXmlObject->xpath( + "//d:prop/" . "$nameSpacePrefix:$propertyName" + ); + Assert::assertArrayHasKey( + 0, + $xmlPart, + "Cannot find property \"$propertyName\"" + ); + Assert::assertEquals( + $propertyValue, + $xmlPart[0]->__toString(), + "\"$propertyName\" has a value \"" . + $xmlPart[0]->__toString() . "\" but \"$propertyValue\" expected" + ); + } + + /** + * @Then /^the response should contain a custom "([^"]*)" property with namespace "([^"]*)" and complex value "(([^"\\]|\\.)*)"$/ + * + * @param string $propertyName + * @param string $namespaceString + * @param string $propertyValue + * + * @return void + * @throws Exception + */ + public function theResponseShouldContainACustomPropertyWithComplexValue( + string $propertyName, + string $namespaceString, + string $propertyValue + ):void { + // let's unescape quotes first + $propertyValue = \str_replace('\"', '"', $propertyValue); + $this->featureContext->setResponseXmlObject( + HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ) + ); + $responseXmlObject = $this->featureContext->getResponseXmlObject(); + //calculate the namespace prefix and namespace + $matches = []; + \preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches); + $nameSpace = $matches[2]; + $nameSpacePrefix = $matches[1]; + $responseXmlObject->registerXPathNamespace( + $nameSpacePrefix, + $nameSpace + ); + $xmlPart = $responseXmlObject->xpath( + "//d:prop/" . "$nameSpacePrefix:$propertyName" . "/*" + ); + Assert::assertArrayHasKey( + 0, + $xmlPart, + "Cannot find property \"$propertyName\"" + ); + Assert::assertEquals( + $propertyValue, + $xmlPart[0]->asXML(), + "\"$propertyName\" has a value \"" . + $xmlPart[0]->asXML() . "\" but \"$propertyValue\" expected" + ); + } + + /** + * @Then the single response should contain a property :property with a child property :childProperty + * + * @param string $property + * @param string $childProperty + * + * @return void + * + * @throws Exception + */ + public function theSingleResponseShouldContainAPropertyWithChildProperty( + string $property, + string $childProperty + ):void { + $xmlPart = $this->featureContext->getResponseXmlObject()->xpath( + "//d:prop/$property/$childProperty" + ); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find property \"$property/$childProperty\"" + ); + } + + /** + * @Then the single response should contain a property :key with value :value + * + * @param string $key + * @param string $expectedValue + * + * @return void + * @throws Exception + */ + public function theSingleResponseShouldContainAPropertyWithValue( + string $key, + string $expectedValue + ):void { + $this->checkSingleResponseContainsAPropertyWithValueAndAlternative( + $key, + $expectedValue, + $expectedValue + ); + } + + /** + * @Then the single response about the file owned by :user should contain a property :key with value :value + * + * @param string $user + * @param string $key + * @param string $expectedValue + * + * @return void + * @throws Exception + */ + public function theSingleResponseAboutTheFileOwnedByShouldContainAPropertyWithValue( + string $user, + string $key, + string $expectedValue + ):void { + $this->checkSingleResponseContainsAPropertyWithValueAndAlternative( + $key, + $expectedValue, + $expectedValue, + $user + ); + } + + /** + * @Then the single response should contain a property :key with value :value or with value :altValue + * + * @param string $key + * @param string $expectedValue + * @param string $altExpectedValue + * + * @return void + * @throws Exception + */ + public function theSingleResponseShouldContainAPropertyWithValueAndAlternative( + string $key, + string $expectedValue, + string $altExpectedValue + ):void { + $this->checkSingleResponseContainsAPropertyWithValueAndAlternative( + $key, + $expectedValue, + $altExpectedValue + ); + } + + /** + * @param string $key + * @param string $expectedValue + * @param string $altExpectedValue + * @param string|null $user + * + * @return void + * @throws Exception + */ + public function checkSingleResponseContainsAPropertyWithValueAndAlternative( + string $key, + string $expectedValue, + string $altExpectedValue, + ?string $user = null + ):void { + $xmlPart = $this->featureContext->getResponseXmlObject()->xpath( + "//d:prop/$key" + ); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find property \"$key\"" + ); + $value = $xmlPart[0]->__toString(); + $expectedValue = $this->featureContext->substituteInLineCodes( + $expectedValue, + $user + ); + $expectedValue = "#^$expectedValue$#"; + $altExpectedValue = "#^$altExpectedValue$#"; + if (\preg_match($expectedValue, $value) !== 1 + && \preg_match($altExpectedValue, $value) !== 1 + ) { + Assert::fail( + "Property \"$key\" found with value \"$value\", " . + "expected \"$expectedValue\" or \"$altExpectedValue\"" + ); + } + } + + /** + * @Then the value of the item :xpath in the response should be :value + * + * @param string $xpath + * @param string $expectedValue + * + * @return void + * @throws Exception + */ + public function assertValueOfItemInResponseIs(string $xpath, string $expectedValue):void { + $this->assertValueOfItemInResponseAboutUserIs( + $xpath, + null, + $expectedValue + ); + } + + /** + * @Then the value of the item :xpath in the response about user :user should be :value + * + * @param string $xpath + * @param string|null $user + * @param string $expectedValue + * + * @return void + * @throws Exception + */ + public function assertValueOfItemInResponseAboutUserIs(string $xpath, ?string $user, string $expectedValue):void { + $resXml = $this->featureContext->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ); + } + $xmlPart = $resXml->xpath($xpath); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find item with xpath \"$xpath\"" + ); + $value = $xmlPart[0]->__toString(); + $user = $this->featureContext->getActualUsername($user); + $expectedValue = $this->featureContext->substituteInLineCodes( + $expectedValue, + $user + ); + + // The expected value can contain /%base_path%/ which can be empty some time + // This will result in urls such as //remote.php, so replace that + $expectedValue = preg_replace("/\/\/remote\.php/i", "/remote.php", $expectedValue); + Assert::assertEquals( + $expectedValue, + $value, + "item \"$xpath\" found with value \"$value\", " . + "expected \"$expectedValue\"" + ); + } + + /** + * @Then the value of the item :xpath in the response about user :user should be :value1 or :value2 + * + * @param string $xpath + * @param string|null $user + * @param string $expectedValue1 + * @param string $expectedValue2 + * + * @return void + * @throws Exception + */ + public function assertValueOfItemInResponseAboutUserIsEitherOr(string $xpath, ?string $user, string $expectedValue1, string $expectedValue2):void { + if (!$expectedValue2) { + $expectedValue2 = $expectedValue1; + } + $resXml = $this->featureContext->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ); + } + $xmlPart = $resXml->xpath($xpath); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find item with xpath \"$xpath\"" + ); + $value = $xmlPart[0]->__toString(); + $user = $this->featureContext->getActualUsername($user); + $expectedValue1 = $this->featureContext->substituteInLineCodes( + $expectedValue1, + $user + ); + + $expectedValue2 = $this->featureContext->substituteInLineCodes( + $expectedValue2, + $user + ); + + // The expected value can contain /%base_path%/ which can be empty some time + // This will result in urls such as //remote.php, so replace that + $expectedValue1 = preg_replace("/\/\/remote\.php/i", "/remote.php", $expectedValue1); + $expectedValue2 = preg_replace("/\/\/remote\.php/i", "/remote.php", $expectedValue2); + $expectedValues = [$expectedValue1, $expectedValue2]; + $isExpectedValueInMessage = \in_array($value, $expectedValues); + Assert::assertTrue($isExpectedValueInMessage, "The actual value \"$value\" is not one of the expected values: \"$expectedValue1\" or \"$expectedValue2\""); + } + + /** + * @Then the value of the item :xpath in the response should match :value + * + * @param string $xpath + * @param string $pattern + * + * @return void + * @throws Exception + */ + public function assertValueOfItemInResponseRegExp(string $xpath, string $pattern):void { + $this->assertValueOfItemInResponseToUserRegExp( + $xpath, + null, + $pattern + ); + } + + /** + * @Then /^as a public the lock discovery property "([^"]*)" of the (?:file|folder|entry) "([^"]*)" should match "([^"]*)"$/ + * + * @param string $xpath + * @param string $path + * @param string $pattern + * + * @return void + * @throws Exception + */ + public function publicGetsThePropertiesOfFolderAndAssertValueOfItemInResponseRegExp(string $xpath, string $path, string $pattern):void { + $propertiesTable = new TableNode([['propertyName'],['d:lockdiscovery']]); + $this->publicGetsThePropertiesOfFolder($path, $propertiesTable); + + $this->featureContext->theHTTPStatusCodeShouldBe('200'); + $this->assertValueOfItemInResponseToUserRegExp( + $xpath, + null, + $pattern + ); + } + + /** + * @Then there should be an entry with href containing :expectedHref in the response to user :user + * + * @param string $expectedHref + * @param string $user + * + * @return void + * @throws Exception + */ + public function assertEntryWithHrefMatchingRegExpInResponseToUser(string $expectedHref, string $user):void { + $resXml = $this->featureContext->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ); + } + + $user = $this->featureContext->getActualUsername($user); + $expectedHref = $this->featureContext->substituteInLineCodes( + $expectedHref, + $user, + ['preg_quote' => ['/']] + ); + + $index = 0; + while (true) { + $index++; + $xpath = "//d:response[$index]/d:href"; + $xmlPart = $resXml->xpath($xpath); + // If we have run out of entries in the response, then fail the test + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find any entry having href with value $expectedHref in response to $user" + ); + $value = $xmlPart[0]->__toString(); + $decodedValue = \rawurldecode($value); + // for folders, decoded value will be like: "/owncloud/core/remote.php/webdav/strängé folder/" + // expected href should be like: "remote.php/webdav/strängé folder/" + // for files, decoded value will be like: "/owncloud/core/remote.php/webdav/strängé folder/file.txt" + // expected href should be like: "remote.php/webdav/strängé folder/file.txt" + $explodeDecoded = \explode('/', $decodedValue); + // get the first item of the expected href. + // i.e remote.php from "remote.php/webdav/strängé folder/file.txt" + // or dav from "dav/spaces/%spaceid%/C++ file.cpp" + $explodeExpected = \explode('/', $expectedHref); + $remotePhpIndex = \array_search($explodeExpected[0], $explodeDecoded); + if ($remotePhpIndex) { + $explodedHrefPartArray = \array_slice($explodeDecoded, $remotePhpIndex); + $actualHrefPart = \implode('/', $explodedHrefPartArray); + if ($this->featureContext->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES) { + // for spaces webdav, space id is included in the href + // space id from our helper is returned as d8c029e0\-2bc9\-4b9a\-8613\-c727e5417f05 + // so we've to remove "\" before every "-" + $expectedHref = str_replace('\-', '-', $expectedHref); + $expectedHref = str_replace('\$', '$', $expectedHref); + $expectedHref = str_replace('\!', '!', $expectedHref); + } + if ($actualHrefPart === $expectedHref) { + break; + } + } + } + } + + /** + * @Then the value of the item :xpath in the response to user :user should match :value + * + * @param string $xpath + * @param string|null $user + * @param string $pattern + * + * @return void + * @throws Exception + */ + public function assertValueOfItemInResponseToUserRegExp(string $xpath, ?string $user, string $pattern):void { + $resXml = $this->featureContext->getResponseXmlObject(); + if ($resXml === null) { + $resXml = HttpRequestHelper::getResponseXml( + $this->featureContext->getResponse(), + __METHOD__ + ); + } + $xmlPart = $resXml->xpath($xpath); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find item with xpath \"$xpath\"" + ); + $user = $this->featureContext->getActualUsername($user); + $value = $xmlPart[0]->__toString(); + $pattern = $this->featureContext->substituteInLineCodes( + $pattern, + $user, + ['preg_quote' => ['/']] + ); + Assert::assertMatchesRegularExpression( + $pattern, + $value, + "item \"$xpath\" found with value \"$value\", " . + "expected to match regex pattern: \"$pattern\"" + ); + } + + /** + * @Then /^as user "([^"]*)" the lock discovery property "([^"]*)" of the (?:file|folder|entry) "([^"]*)" should match "([^"]*)"$/ + * + * @param string|null $user + * @param string $xpath + * @param string $path + * @param string $pattern + * + * @return void + * @throws Exception + */ + public function userGetsPropertiesOfFolderAndAssertValueOfItemInResponseToUserRegExp(string $user, string $xpath, string $path, string $pattern):void { + $propertiesTable = new TableNode([['propertyName'],['d:lockdiscovery']]); + $this->userGetsPropertiesOfFolder( + $user, + $path, + $propertiesTable + ); + + $this->featureContext->theHTTPStatusCodeShouldBe('200'); + $this->assertValueOfItemInResponseToUserRegExp( + $xpath, + $user, + $pattern + ); + } + + /** + * @Then the item :xpath in the response should not exist + * + * @param string $xpath + * + * @return void + * @throws Exception + */ + public function assertItemInResponseDoesNotExist(string $xpath):void { + $xmlPart = $this->featureContext->getResponseXmlObject()->xpath($xpath); + Assert::assertFalse( + isset($xmlPart[0]), + "Found item with xpath \"$xpath\" but it should not exist" + ); + } + + /** + * @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should contain a property "([^"]*)" with value "([^"]*)" or with value "([^"]*)"$/ + * + * @param string $user + * @param string $path + * @param string $property + * @param string $expectedValue + * @param string $altExpectedValue + * + * @return void + * @throws Exception + */ + public function asUserFolderShouldContainAPropertyWithValueOrWithValue( + string $user, + string $path, + string $property, + string $expectedValue, + string $altExpectedValue + ):void { + $this->featureContext->setResponseXmlObject( + $this->featureContext->listFolderAndReturnResponseXml( + $user, + $path, + '0', + [$property] + ) + ); + $this->theSingleResponseShouldContainAPropertyWithValueAndAlternative( + $property, + $expectedValue, + $altExpectedValue + ); + } + + /** + * @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should contain a property "([^"]*)" with value "([^"]*)"$/ + * + * @param string $user + * @param string $path + * @param string $property + * @param string $value + * + * @return void + * @throws Exception + */ + public function asUserFolderShouldContainAPropertyWithValue( + string $user, + string $path, + string $property, + string $value + ):void { + $this->asUserFolderShouldContainAPropertyWithValueOrWithValue( + $user, + $path, + $property, + $value, + $value + ); + } + + /** + * @Then the single response should contain a property :key with value like :regex + * + * @param string $key + * @param string $regex + * + * @return void + * @throws Exception + */ + public function theSingleResponseShouldContainAPropertyWithValueLike( + string $key, + string $regex + ):void { + $xmlPart = $this->featureContext->getResponseXmlObject()->xpath( + "//d:prop/$key" + ); + Assert::assertTrue( + isset($xmlPart[0]), + "Cannot find property \"$key\"" + ); + $value = $xmlPart[0]->__toString(); + Assert::assertMatchesRegularExpression( + $regex, + $value, + "Property \"$key\" found with value \"$value\", expected \"$regex\"" + ); + } + + /** + * @Then the response should contain a share-types property with + * + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function theResponseShouldContainAShareTypesPropertyWith(TableNode $table):void { + $this->featureContext->verifyTableNodeColumnsCount($table, 1); + WebdavTest::assertResponseContainsShareTypes( + $this->featureContext->getResponseXmlObject(), + $table->getRows() + ); + } + + /** + * @Then the response should contain an empty property :property + * + * @param string $property + * + * @return void + * @throws Exception + */ + public function theResponseShouldContainAnEmptyProperty(string $property):void { + $xmlPart = $this->featureContext->getResponseXmlObject()->xpath( + "//d:prop/$property" + ); + Assert::assertCount( + 1, + $xmlPart, + "Cannot find property \"$property\"" + ); + Assert::assertEmpty( + $xmlPart[0], + "Property \"$property\" is not empty" + ); + } + + /** + * @param string $user + * @param string $path + * @param string|null $storePath + * + * @return void + * @throws Exception + */ + public function storeEtagOfElement(string $user, string $path, ?string $storePath = ""):void { + if ($storePath === "") { + $storePath = $path; + } + $user = $this->featureContext->getActualUsername($user); + $propertiesTable = new TableNode([['propertyName'],['getetag']]); + $this->userGetsPropertiesOfFolder( + $user, + $path, + $propertiesTable + ); + $this->storedETAG[$user][$storePath] + = $this->featureContext->getEtagFromResponseXmlObject(); + } + + /** + * @When user :user stores etag of element :path using the WebDAV API + * + * @param string $user + * @param string $path + * + * @return void + * @throws Exception + */ + public function userStoresEtagOfElement(string $user, string $path):void { + $this->storeEtagOfElement( + $user, + $path + ); + } + + /** + * @Given user :user has stored etag of element :path on path :storePath + * + * @param string $user + * @param string $path + * @param string $storePath + * + * @return void + * @throws Exception + */ + public function userStoresEtagOfElementOnPath(string $user, string $path, string $storePath):void { + $user = $this->featureContext->getActualUsername($user); + $this->storeEtagOfElement( + $user, + $path, + $storePath + ); + if ($storePath == "") { + $storePath = $path; + } + if ($this->storedETAG[$user][$storePath] === null || $this->storedETAG[$user][$path] === "") { + throw new Exception("Expected stored etag to be some string but found null!"); + } + } + + /** + * @Given user :user has stored etag of element :path + * + * @param string $user + * @param string $path + * + * @return void + * @throws Exception + */ + public function userHasStoredEtagOfElement(string $user, string $path):void { + $user = $this->featureContext->getActualUsername($user); + $this->storeEtagOfElement( + $user, + $path + ); + if ($this->storedETAG[$user][$path] === "" || $this->storedETAG[$user][$path] === null) { + throw new Exception("Expected stored etag to be some string but found null!"); + } + } + + /** + * @Then /^the properties response should contain an etag$/ + * + * @return void + * @throws Exception + */ + public function thePropertiesResponseShouldContainAnEtag():void { + Assert::assertTrue( + $this->featureContext->isEtagValid(), + __METHOD__ + . " getetag not found in response" + ); + } + + /** + * @Then as user :username the last response should have the following properties + * + * only supports new DAV version + * + * @param string $username + * @param TableNode $expectedPropTable with following columns: + * resource: full path of resource(file/folder/entry) from root of your oc storage + * property: expected name of property to be asserted, eg: status, href, customPropName + * value: expected value of expected property + * + * @return void + * @throws Exception + */ + public function theResponseShouldHavePropertyWithValue(string $username, TableNode $expectedPropTable):void { + $username = $this->featureContext->getActualUsername($username); + $this->featureContext->verifyTableNodeColumns($expectedPropTable, ['resource', 'propertyName', 'propertyValue']); + $responseXmlObject = $this->featureContext->getResponseXmlObject(); + + $hrefSplittedUptoUsername = \explode("/", (string)$responseXmlObject->xpath("//d:href")[0]); + $xmlHrefSplittedArray = \array_slice( + $hrefSplittedUptoUsername, + 0, + \array_search($username, $hrefSplittedUptoUsername) + 1 + ); + $xmlHref = \implode("/", $xmlHrefSplittedArray); + foreach ($expectedPropTable->getColumnsHash() as $col) { + if ($col["propertyName"] === "status") { + $xmlPart = $responseXmlObject->xpath( + "//d:href[.='" . + $xmlHref . $col["resource"] . + "']/following-sibling::d:propstat//d:" . + $col["propertyName"] + ); + } else { + $xmlPart = $responseXmlObject->xpath( + "//d:href[.= '" . + $xmlHref . $col["resource"] . + "']/..//oc:" . $col["propertyName"] + ); + } + Assert::assertEquals( + $col["propertyValue"], + $xmlPart[0], + __METHOD__ + . " Expected '" . $col["propertyValue"] . "' but got '" . $xmlPart[0] . "'" + ); + } + } + + /** + * @param string $path + * @param string $user + * + * @return string + * @throws Exception + */ + public function getCurrentEtagOfElement(string $path, string $user):string { + $user = $this->featureContext->getActualUsername($user); + $propertiesTable = new TableNode([['propertyName'],['getetag']]); + $this->userGetsPropertiesOfFolder( + $user, + $path, + $propertiesTable + ); + return $this->featureContext->getEtagFromResponseXmlObject(); + } + + /** + * @param string $path + * @param string $user + * @param string $messageStart + * + * @return string + */ + public function getStoredEtagOfElement(string $path, string $user, string $messageStart = ''):string { + if ($messageStart === '') { + $messageStart = __METHOD__; + } + Assert::assertArrayHasKey( + $user, + $this->storedETAG, + $messageStart + . " Trying to check etag of element $path of user $user but the user does not have any stored etags" + ); + Assert::assertArrayHasKey( + $path, + $this->storedETAG[$user], + $messageStart + . " Trying to check etag of element $path of user $user but the user does not have a stored etag for the element" + ); + return $this->storedETAG[$user][$path]; + } + + /** + * @Then these etags should not have changed: + * + * @param TableNode $etagTable + * + * @return void + * @throws Exception + */ + public function theseEtagsShouldNotHaveChanged(TableNode $etagTable):void { + $this->featureContext->verifyTableNodeColumns($etagTable, ["user", "path"]); + $this->featureContext->verifyTableNodeColumnsCount($etagTable, 2); + $changedEtagCount = 0; + $changedEtagMessage = __METHOD__; + foreach ($etagTable->getColumnsHash() as $row) { + $user = $row["user"]; + $path = $row["path"]; + $user = $this->featureContext->getActualUsername($user); + $actualEtag = $this->getCurrentEtagOfElement($path, $user); + $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); + if ($actualEtag !== $storedEtag) { + $changedEtagCount = $changedEtagCount + 1; + $changedEtagMessage + .= "\nThe etag '$storedEtag' of element '$path' of user '$user' changed to '$actualEtag'."; + } + } + Assert::assertEquals(0, $changedEtagCount, $changedEtagMessage); + } + + /** + * @Then the etag of element :path of user :user should not have changed + * + * @param string $path + * @param string $user + * + * @return void + * @throws Exception + */ + public function etagOfElementOfUserShouldNotHaveChanged(string $path, string $user):void { + $user = $this->featureContext->getActualUsername($user); + $actualEtag = $this->getCurrentEtagOfElement($path, $user); + $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); + Assert::assertEquals( + $storedEtag, + $actualEtag, + __METHOD__ + . " The etag of element '$path' of user '$user' was not expected to change." + . " The stored etag was '$storedEtag' but got '$actualEtag' from the response" + ); + } + + /** + * @Then these etags should have changed: + * + * @param TableNode $etagTable + * + * @return void + * @throws Exception + */ + public function theseEtagsShouldHaveChanged(TableNode $etagTable):void { + $this->featureContext->verifyTableNodeColumns($etagTable, ["user", "path"]); + $this->featureContext->verifyTableNodeColumnsCount($etagTable, 2); + $unchangedEtagCount = 0; + $unchangedEtagMessage = __METHOD__; + foreach ($etagTable->getColumnsHash() as $row) { + $user = $row["user"]; + $path = $row["path"]; + $user = $this->featureContext->getActualUsername($user); + $actualEtag = $this->getCurrentEtagOfElement($path, $user); + $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); + if ($actualEtag === $storedEtag) { + $unchangedEtagCount = $unchangedEtagCount + 1; + $unchangedEtagMessage + .= "\nThe etag '$storedEtag' of element '$path' of user '$user' did not change."; + } + } + Assert::assertEquals(0, $unchangedEtagCount, $unchangedEtagMessage); + } + + /** + * @Then the etag of element :path of user :user should have changed + * + * @param string $path + * @param string $user + * + * @return void + * @throws Exception + */ + public function etagOfElementOfUserShouldHaveChanged(string $path, string $user):void { + $user = $this->featureContext->getActualUsername($user); + $actualEtag = $this->getCurrentEtagOfElement($path, $user); + $storedEtag = $this->getStoredEtagOfElement($path, $user, __METHOD__); + Assert::assertNotEquals( + $storedEtag, + $actualEtag, + __METHOD__ + . " The etag of element '$path' of user '$user' was expected to change." + . " The stored etag was '$storedEtag' and also got '$actualEtag' from the response" + ); + } + + /** + * @Then the etag of element :path of user :user on server :server should have changed + * + * @param string $path + * @param string $user + * @param string $server + * + * @return void + * @throws Exception + */ + public function theEtagOfElementOfUserOnServerShouldHaveChanged( + string $path, + string $user, + string $server + ):void { + $previousServer = $this->featureContext->usingServer($server); + $this->etagOfElementOfUserShouldHaveChanged($path, $user); + $this->featureContext->usingServer($previousServer); + } + + /** + * @Then the etag of element :path of user :user on server :server should not have changed + * + * @param string $path + * @param string $user + * @param string $server + * + * @return void + * @throws Exception + */ + public function theEtagOfElementOfUserOnServerShouldNotHaveChanged( + string $path, + string $user, + string $server + ):void { + $previousServer = $this->featureContext->usingServer($server); + $this->etagOfElementOfUserShouldNotHaveChanged($path, $user); + $this->featureContext->usingServer($previousServer); + } + + /** + * This will run before EVERY scenario. + * It will set the properties for this object. + * + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + */ + public function before(BeforeScenarioScope $scope):void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + } +} diff --git a/tests/acceptance/features/bootstrap/bootstrap.php b/tests/acceptance/features/bootstrap/bootstrap.php index a8302894b20..fc5815031be 100644 --- a/tests/acceptance/features/bootstrap/bootstrap.php +++ b/tests/acceptance/features/bootstrap/bootstrap.php @@ -1,4 +1,5 @@ addPsr4( - "", - $pathToCore . "/tests/acceptance/features/bootstrap", - true -); $classLoader->addPsr4("TestHelpers\\", __DIR__ . "/../../../TestHelpers", true); $classLoader->register(); + +// while running for the local API tests, the tests code from ownCloud/core is not used +// so we need the constants to be defined for the tests to use them, but for the case where, +// the tests are running for oC/core API tests, the constants are already defined in the bootstrap.php there +// so we do not declare them again to avoid the "already defined" error + +// Sleep for 10 milliseconds +if (!\defined('STANDARD_SLEEP_TIME_MILLISEC')) { + \define('STANDARD_SLEEP_TIME_MILLISEC', 10); +} + +if (!\defined('STANDARD_SLEEP_TIME_MICROSEC')) { + \define('STANDARD_SLEEP_TIME_MICROSEC', STANDARD_SLEEP_TIME_MILLISEC * 1000); +} + +// Long timeout for use in code that needs to wait for known slow UI +if (!\defined('LONG_UI_WAIT_TIMEOUT_MILLISEC')) { + \define('LONG_UI_WAIT_TIMEOUT_MILLISEC', 60000); +} + +// Default timeout for use in code that needs to wait for the UI +if (!\defined('STANDARD_UI_WAIT_TIMEOUT_MILLISEC')) { + \define('STANDARD_UI_WAIT_TIMEOUT_MILLISEC', 10000); +} + +// Minimum timeout for use in code that needs to wait for the UI +if (!\defined('MINIMUM_UI_WAIT_TIMEOUT_MILLISEC')) { + \define('MINIMUM_UI_WAIT_TIMEOUT_MILLISEC', 500); +} + +if (!\defined('MINIMUM_UI_WAIT_TIMEOUT_MICROSEC')) { + \define('MINIMUM_UI_WAIT_TIMEOUT_MICROSEC', MINIMUM_UI_WAIT_TIMEOUT_MILLISEC * 1000); +} + +// Minimum timeout for emails +if (!\defined('EMAIL_WAIT_TIMEOUT_SEC')) { + \define('EMAIL_WAIT_TIMEOUT_SEC', 10); +} +if (!\defined('EMAIL_WAIT_TIMEOUT_MILLISEC')) { + \define('EMAIL_WAIT_TIMEOUT_MILLISEC', EMAIL_WAIT_TIMEOUT_SEC * 1000); +} + +// Default number of times to retry where retries are useful +if (!\defined('STANDARD_RETRY_COUNT')) { + \define('STANDARD_RETRY_COUNT', 5); +} +// Minimum number of times to retry where retries are useful +if (!\defined('MINIMUM_RETRY_COUNT')) { + \define('MINIMUM_RETRY_COUNT', 2); +} + +// The remote server-under-test might or might not happen to have this directory. +// If it does not exist, then the tests may end up creating it. +if (!\defined('ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER')) { + \define('ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER', 'tests/acceptance'); +} + +// The following directory should NOT already exist on the remote server-under-test. +// Acceptance tests are free to do anything needed in this directory, and to +// delete it during or at the end of testing. +if (!\defined('TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER')) { + \define('TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER', ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER . '/server_tmp'); +} + +// The following directory is created, used, and deleted by tests that need to +// use some "local external storage" on the server. +if (!\defined('LOCAL_STORAGE_DIR_ON_REMOTE_SERVER')) { + \define('LOCAL_STORAGE_DIR_ON_REMOTE_SERVER', TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . '/local_storage'); +} diff --git a/tests/acceptance/features/coreApiAuth/webDavAuth.feature b/tests/acceptance/features/coreApiAuth/webDavAuth.feature new file mode 100644 index 00000000000..fadc2cbeee9 --- /dev/null +++ b/tests/acceptance/features/coreApiAuth/webDavAuth.feature @@ -0,0 +1,40 @@ +@api +Feature: auth + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario: using WebDAV anonymously + When a user requests "/remote.php/webdav" with "PROPFIND" and no authentication + Then the HTTP status code should be "401" + + @smokeTest @skipOnOcV10 @personalSpace + Scenario: using spaces WebDAV anonymously + When user "Alice" requests "/dav/spaces/%spaceid%" with "PROPFIND" and no authentication + Then the HTTP status code should be "401" + + @smokeTest + Scenario Outline: using WebDAV with basic auth + When user "Alice" requests "" with "PROPFIND" using basic auth + Then the HTTP status code should be "207" + Examples: + | dav_path | + | /remote.php/webdav | + + @skipOnOcV10 @personalSpace + Examples: + | dav_path | + | /dav/spaces/%spaceid% | + + @smokeTest @notToImplementOnOCIS @issue-ocis-reva-28 + Scenario: using WebDAV with token auth + Given a new client token for "Alice" has been generated + When user "Alice" requests "/remote.php/webdav" with "PROPFIND" using basic token auth + Then the HTTP status code should be "207" + + @smokeTest @notToImplementOnOCIS + Scenario: using WebDAV with browser session + Given a new browser session for "Alice" has been started + When the user requests "/remote.php/webdav" with "PROPFIND" using the browser session + Then the HTTP status code should be "207" diff --git a/tests/acceptance/features/coreApiAuthOcs/ocsDELETEAuth.feature b/tests/acceptance/features/coreApiAuthOcs/ocsDELETEAuth.feature new file mode 100644 index 00000000000..676cf84b26c --- /dev/null +++ b/tests/acceptance/features/coreApiAuthOcs/ocsDELETEAuth.feature @@ -0,0 +1,30 @@ +@api @files_sharing-app-required +Feature: auth + + Background: + Given user "another-admin" has been created with default attributes and without skeleton files + + @smokeTest @issue-ocis-reva-30 @issue-ocis-reva-65 @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @issue-32068 + Scenario: send DELETE requests to OCS endpoints as admin with wrong password + Given user "another-admin" has been added to group "admin" + When user "another-admin" requests these endpoints with "DELETE" using password "invalid" about user "Alice" + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/123 | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/shares/123 | + | /ocs/v1.php/apps/files_sharing/api/v1/shares/pending/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123 | + | /ocs/v1.php/cloud/apps/testing | + | /ocs/v2.php/cloud/apps/testing | + | /ocs/v1.php/cloud/groups/group1 | + | /ocs/v2.php/cloud/groups/group1 | + | /ocs/v1.php/cloud/users/%username% | + | /ocs/v2.php/cloud/users/%username% | + | /ocs/v1.php/cloud/users/%username%/groups | + | /ocs/v2.php/cloud/users/%username%/groups | + | /ocs/v1.php/cloud/users/%username%/subadmins | + | /ocs/v2.php/cloud/users/%username%/subadmins | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "401" diff --git a/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature b/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature new file mode 100644 index 00000000000..9bdba239a16 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthOcs/ocsGETAuth.feature @@ -0,0 +1,248 @@ +@api @files_sharing-app-required +Feature: auth + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @issue-32068 @skipOnOcV10 @issue-ocis-reva-30 @smokeTest + Scenario: using OCS anonymously + When a user requests these endpoints with "GET" and no authentication + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/cloud/apps | + | /ocs/v2.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + | /ocs/v1.php/privatedata/getattribute | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "401" + + @issue-ocis-reva-29 + Scenario: ocs config end point accessible by unauthorized users + When a user requests these endpoints with "GET" and no authentication + | endpoint | + | /ocs/v1.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When a user requests these endpoints with "GET" and no authentication + | endpoint | + | /ocs/v2.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + + @issue-32068 @skipOnOcV10 @issue-ocis-reva-11 @issue-ocis-reva-30 @issue-ocis-reva-31 @issue-ocis-reva-32 @issue-ocis-reva-33 @issue-ocis-reva-34 @issue-ocis-reva-35 + Scenario: using OCS with non-admin basic auth + When the user "Alice" requests these endpoints with "GET" with basic auth + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/config | + | /ocs/v1.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When the user "Alice" requests these endpoints with "GET" with basic auth + | endpoint | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/config | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + When the user "Alice" requests these endpoints with "GET" with basic auth + | endpoint | + | /ocs/v1.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/apps | + | /ocs/v2.php/cloud/groups | + | /ocs/v2.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "401" + + @issue-32068 @skipOnOcV10 @issue-ocis-reva-29 @issue-ocis-reva-30 @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: using OCS as normal user with wrong password + When user "Alice" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/cloud/apps | + | /ocs/v2.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + | /ocs/v1.php/privatedata/getattribute | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "401" + When user "Alice" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v1.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When user "Alice" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v2.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + + @issue-ocis-reva-65 + Scenario:using OCS with admin basic auth + When the administrator requests these endpoint with "GET" + | endpoint | + | /ocs/v1.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v1.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When the administrator requests these endpoint with "GET" + | endpoint | + | /ocs/v2.php/cloud/apps | + | /ocs/v2.php/cloud/groups | + | /ocs/v2.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + + @issue-ocis-reva-30 @issue-ocis-reva-65 @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: using OCS as admin user with wrong password + Given user "another-admin" has been created with default attributes and without skeleton files + And user "another-admin" has been added to group "admin" + When user "another-admin" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/cloud/apps | + | /ocs/v2.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + | /ocs/v1.php/privatedata/getattribute | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" + When user "another-admin" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v1.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When user "another-admin" requests these endpoints with "GET" using password "invalid" + | endpoint | + | /ocs/v2.php/config | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + + @notToImplementOnOCIS @issue-ocis-reva-30 @issue-ocis-reva-28 + Scenario: using OCS with token auth of a normal user + Given a new client token for "Alice" has been generated + When user "Alice" requests these endpoints with "GET" using basic token auth + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/config | + | /ocs/v1.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When user "Alice" requests these endpoints with "GET" using basic token auth + | endpoint | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/config | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + When user "Alice" requests these endpoints with "GET" using basic token auth + | endpoint | + | /ocs/v1.php/cloud/apps | + | /ocs/v1.php/cloud/users | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/apps | + | /ocs/v2.php/cloud/groups | + | /ocs/v2.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" + + @notToImplementOnOCIS + Scenario: using OCS with browser session of normal user + Given a new browser session for "Alice" has been started + When the user requests these endpoints with "GET" using a new browser session + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/config | + | /ocs/v1.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When the user requests these endpoints with "GET" using a new browser session + | endpoint | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/config | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + When the user requests these endpoints with "GET" using a new browser session + | endpoint | + | /ocs/v1.php/cloud/apps | + | /ocs/v2.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" + + @notToImplementOnOCIS + Scenario: using OCS with an app password of a normal user + Given a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "GET" using the generated app password + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/config | + | /ocs/v1.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + When the user requests these endpoints with "GET" using the generated app password + | endpoint | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/config | + | /ocs/v2.php/privatedata/getattribute | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "200" + When the user requests these endpoints with "GET" using the generated app password + | endpoint | + | /ocs/v1.php/cloud/apps | + | /ocs/v2.php/cloud/apps | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" diff --git a/tests/acceptance/features/coreApiAuthOcs/ocsPOSTAuth.feature b/tests/acceptance/features/coreApiAuthOcs/ocsPOSTAuth.feature new file mode 100644 index 00000000000..9b10a64ee6f --- /dev/null +++ b/tests/acceptance/features/coreApiAuthOcs/ocsPOSTAuth.feature @@ -0,0 +1,42 @@ +@api @files_sharing-app-required +Feature: auth + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @issue-ocis-reva-30 @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send POST requests to OCS endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/123 | + | /ocs/v1.php/apps/files_sharing/api/v1/shares | + | /ocs/v2.php/apps/files_sharing/api/v1/shares | + | /ocs/v1.php/apps/files_sharing/api/v1/shares/pending/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123 | + | /ocs/v1.php/cloud/apps/testing | + | /ocs/v2.php/cloud/apps/testing | + | /ocs/v1.php/cloud/groups | + | /ocs/v2.php/cloud/groups | + | /ocs/v1.php/cloud/users | + | /ocs/v2.php/cloud/users | + | /ocs/v1.php/cloud/users/%username%/groups | + | /ocs/v2.php/cloud/users/%username%/groups | + | /ocs/v1.php/cloud/users/%username%/subadmins | + | /ocs/v2.php/cloud/users/%username%/subadmins | + | /ocs/v1.php/privatedata/deleteattribute/testing/test | + | /ocs/v2.php/privatedata/deleteattribute/testing/test | + | /ocs/v1.php/privatedata/setattribute/testing/test | + | /ocs/v2.php/privatedata/setattribute/testing/test | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /ocs/v1.php/person/check | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "101" + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /ocs/v2.php/person/check | + Then the HTTP status code of responses on all endpoints should be "400" + And the OCS status code of responses on all endpoints should be "400" diff --git a/tests/acceptance/features/coreApiAuthOcs/ocsPUTAuth.feature b/tests/acceptance/features/coreApiAuthOcs/ocsPUTAuth.feature new file mode 100644 index 00000000000..c6101795534 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthOcs/ocsPUTAuth.feature @@ -0,0 +1,31 @@ +@api @files_sharing-app-required +Feature: auth + + Background: + Given user "another-admin" has been created with default attributes and without skeleton files + + @issue-ocis-reva-30 @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PUT request to OCS endpoints as admin with wrong password + Given user "another-admin" has been added to group "admin" + When user "another-admin" requests these endpoints with "PUT" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /ocs/v1.php/cloud/users/%username% | + | /ocs/v2.php/cloud/users/%username% | + | /ocs/v1.php/cloud/users/%username%/disable | + | /ocs/v2.php/cloud/users/%username%/disable | + | /ocs/v1.php/cloud/users/%username%/enable | + | /ocs/v2.php/cloud/users/%username%/enable | + | /ocs/v1.php/apps/files_sharing/api/v1/shares/123 | + | /ocs/v2.php/apps/files_sharing/api/v1/shares/123 | + Then the HTTP status code of responses on all endpoints should be "401" + And the OCS status code of responses on all endpoints should be "997" + + @issue-38423 @skipOnOcV10 + Scenario: Request to edit nonexistent user by authorized admin gets unauthorized in http response + Given user "another-admin" has been added to group "admin" + When user "another-admin" requests these endpoints with "PUT" including body "doesnotmatter" about user "nonexistent" + | endpoint | + | /ocs/v1.php/cloud/users/%username% | + | /ocs/v2.php/cloud/users/%username% | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "101" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavCOPYAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavCOPYAuth.feature new file mode 100644 index 00000000000..bb1317fbaf1 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavCOPYAuth.feature @@ -0,0 +1,184 @@ +@api +Feature: COPY file/folder + + As a user + I want to copy a file or folder + So that I can duplicate it + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send COPY requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "COPY" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "COPY" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send COPY requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "COPY" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "COPY" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-14 + Scenario: send COPY requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "COPY" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + @skipOnOcV10 @personalSpace @issue-ocis-reva-14 + Scenario: send COPY requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "COPY" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + + Scenario: send COPY requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "COPY" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "COPY" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send COPY requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "COPY" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "COPY" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send COPY requests to webDav endpoints without any authentication + When a user requests these endpoints with "COPY" with no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "COPY" with no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send COPY requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "COPY" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send COPY requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "COPY" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + # The token was valid and accepted but the body is invalid so it gives 403 + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "403" + + @skipOnOcV10 + Scenario: send COPY requests to webDav endpoints with body as normal user + When user "Alice" requests these endpoints with "COPY" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/webdav/PARENT/parent.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "415" + + @skipOnOcV10 @personalSpace + Scenario: send COPY requests to webDav endpoints with body as normal user using the spaces WebDAV API + When user "Alice" requests these endpoints with "COPY" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "415" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature new file mode 100644 index 00000000000..c905a7180b8 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavDELETEAuth.feature @@ -0,0 +1,204 @@ +@api +Feature: delete file/folder + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send DELETE requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "DELETE" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/webdav/PARENT/parent.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "DELETE" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + Scenario: send DELETE requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "DELETE" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/webdav/PARENT/parent.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "DELETE" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-13 + Scenario: send DELETE requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "DELETE" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @issue-ocis-reva-13 @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "DELETE" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @smokeTest + Scenario: send DELETE requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "DELETE" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "DELETE" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send DELETE requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "DELETE" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "DELETE" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send DELETE requests to webDav endpoints without any authentication + When a user requests these endpoints with "DELETE" with no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "DELETE" with no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-60 + Scenario: send DELETE requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "DELETE" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-60 @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints using token authentication should not work using the spaces WebDAV API + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "DELETE" using the generated app password about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-60 + Scenario: send DELETE requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "DELETE" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "204" + + @issue-ocis-reva-60 @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints using app password token as password using the spaces WebDAV API + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "DELETE" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "204" + + @skipOnOcV10 + Scenario: send DELETE requests to webDav endpoints with body as normal user + When user "Alice" requests these endpoints with "DELETE" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "415" + + @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints with body as normal user using the spaces WebDAV API + When user "Alice" requests these endpoints with "DELETE" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + Then the HTTP status code of responses on all endpoints should be "415" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavLOCKAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavLOCKAuth.feature new file mode 100644 index 00000000000..623a6b36783 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavLOCKAuth.feature @@ -0,0 +1,165 @@ +@api +Feature: LOCK file/folder + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send LOCK requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "LOCK" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "LOCK" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send LOCK requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "LOCK" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "LOCK" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-9 + Scenario: send LOCK requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "LOCK" to get property "d:shared" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + Then the HTTP status code of responses on all endpoints should be "403" + When user "Brian" requests these endpoints with "LOCK" to get property "d:shared" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "409" + + @issue-ocis-reva-9 @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "LOCK" to get property "d:shared" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + Then the HTTP status code of responses on all endpoints should be "403" + When user "Brian" requests these endpoints with "LOCK" to get property "d:shared" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "409" + + + Scenario: send LOCK requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "LOCK" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "LOCK" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send LOCK requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "LOCK" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "LOCK" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send LOCK requests to webDav endpoints without any authentication + When a user requests these endpoints with "LOCK" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "LOCK" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send LOCK requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "LOCK" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send LOCK requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "LOCK" to get property "d:shared" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "200" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavMKCOLAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavMKCOLAuth.feature new file mode 100644 index 00000000000..9f49fd4d650 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavMKCOLAuth.feature @@ -0,0 +1,183 @@ +@api +Feature: create folder using MKCOL + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MKCOL requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "MKCOL" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send MKCOL requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "MKCOL" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MKCOL requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "MKCOL" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnOcV10 @personalSpace @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MKCOL requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "MKCOL" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @issue-ocis-5049 @issue-ocis-reva-9 @issue-ocis-reva-197 + Scenario: send MKCOL requests to another user's webDav endpoints as normal user + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/does-not-exist | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @skipOnOcV10 @issue-ocis-5049 @issue-ocis-reva-9 @issue-ocis-reva-197 + Scenario: send MKCOL requests to non-existent user's webDav endpoints as normal user + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "" about user "non-existent-user" + | endpoint | + | /remote.php/dav/files/non-existent-user/textfile0.txt | + | /remote.php/dav/files/non-existent-user/PARENT | + | /remote.php/dav/files/non-existent-user/does-not-exist | + | /remote.php/dav/files/non-existent-user/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @skipOnOcV10 @personalSpace @issue-ocis-reva-9 @issue-ocis-reva-197 + Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/does-not-exist | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @skipOnOcV10 @issue-ocis-5049 @personalSpace @issue-ocis-reva-9 @issue-ocis-reva-197 + Scenario: send MKCOL requests to non-existent user's webDav endpoints as normal user using the spaces WebDAV API + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "" about user "non-existent-user" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/does-not-exist | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + + Scenario: send MKCOL requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "MKCOL" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send MKCOL requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "MKCOL" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send MKCOL requests to webDav endpoints using valid password and username of different user + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send MKCOL requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "MKCOL" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MKCOL requests to webDav endpoints without any authentication + When a user requests these endpoints with "MKCOL" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send MKCOL requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "MKCOL" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send MKCOL requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "MKCOL" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send MKCOL requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "MKCOL" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/newCol | + | /remote.php/dav/files/%username%/newCol1 | + | /remote.php/dav/files/%username%/PARENT/newCol | + | /remote.php/webdav/COL | + | /remote.php/dav/files/%username%/FOLDER/newCol | + Then the HTTP status code of responses on all endpoints should be "201" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavMOVEAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavMOVEAuth.feature new file mode 100644 index 00000000000..b2e2723b5b7 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavMOVEAuth.feature @@ -0,0 +1,180 @@ +@api +Feature: MOVE file/folder + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MOVE requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "MOVE" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "MOVE" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MOVE requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "MOVE" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "MOVE" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-14 + Scenario: send MOVE requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "MOVE" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + @skipOnOcV10 @personalSpace @issue-ocis-reva-14 + Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "MOVE" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + + Scenario: send MOVE requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "MOVE" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "MOVE" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send MOVE requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "MOVE" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "MOVE" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send MOVE requests to webDav endpoints without any authentication + When a user requests these endpoints with "MOVE" with no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "MOVE" with no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send MOVE requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "MOVE" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send MOVE requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "MOVE" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + # The token was valid and accepted but the body is invalid so it gives 403 + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "403" + + @skipOnOcV10 + Scenario: send MOVE requests to webDav endpoints with body as normal user + When user "Alice" requests these endpoints with "MOVE" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/webdav/PARENT/parent.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "415" + + @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints with body as normal user using the spaces WebDAV API + When user "Alice" requests these endpoints with "MOVE" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "415" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavPOSTAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavPOSTAuth.feature new file mode 100644 index 00000000000..1e90596219f --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavPOSTAuth.feature @@ -0,0 +1,160 @@ +@api +Feature: get file info using POST + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send POST requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send POST requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-179 + Scenario: send POST requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "POST" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @skipOnOcV10 @personalSpace @issue-ocis-reva-179 + Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "POST" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + + Scenario: send POST requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "POST" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "POST" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send POST requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "POST" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "POST" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send POST requests to webDav endpoints without any authentication + When a user requests these endpoints with "POST" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "POST" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send POST requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "POST" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send POST requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "POST" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + # this method is not available so gives 501 + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "501" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavPROPFINDAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavPROPFINDAuth.feature new file mode 100644 index 00000000000..d1bfee252d5 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavPROPFINDAuth.feature @@ -0,0 +1,158 @@ +@api +Feature: get file info using PROPFIND + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPFIND requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "PROPFIND" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PROPFIND" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPFIND requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "PROPFIND" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PROPFIND" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-9 + Scenario: send PROPFIND requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "PROPFIND" to get property "d:getetag" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @skipOnOcV10 @personalSpace @issue-ocis-reva-9 + Scenario: send PROPFIND requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PROPFIND" to get property "d:getetag" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + + Scenario: send PROPFIND requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "PROPFIND" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "PROPFIND" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send PROPFIND requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "PROPFIND" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PROPFIND" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPFIND requests to webDav endpoints without any authentication + When a user requests these endpoints with "PROPFIND" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "PROPFIND" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PROPFIND requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "PROPFIND" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PROPFIND requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "PROPFIND" to get property "d:getetag" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/webdav/textfile0.txt | + Then the HTTP status code of responses on all endpoints should be "207" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavPROPPATCHAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavPROPPATCHAuth.feature new file mode 100644 index 00000000000..3c1b893b9cc --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavPROPPATCHAuth.feature @@ -0,0 +1,159 @@ +@api +Feature: PROPPATCH file/folder + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPPATCH requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPPATCH requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @issue-ocis-reva-9 @issue-ocis-reva-197 + Scenario: send PROPPATCH requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "PROPPATCH" to set property "favorite" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + @issue-ocis-reva-9 @issue-ocis-reva-197 @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PROPPATCH" to set property "favorite" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + + Scenario: send PROPPATCH requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send PROPPATCH requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PROPPATCH" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PROPPATCH requests to webDav endpoints without any authentication + When a user requests these endpoints with "PROPPATCH" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "PROPPATCH" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PROPPATCH requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "PROPPATCH" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PROPPATCH requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "PROPPATCH" to set property "favorite" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "207" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavPUTAuth.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavPUTAuth.feature new file mode 100644 index 00000000000..b12dba719e0 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavPUTAuth.feature @@ -0,0 +1,174 @@ +@api +Feature: get file info using PUT + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PUT requests to webDav endpoints as normal user with wrong password + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints as normal user with wrong password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PUT requests to webDav endpoints as normal user with no password + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints as normal user with no password using the spaces WebDAV API + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 + Scenario: send PUT requests to another user's webDav endpoints as normal user + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT | + Then the HTTP status code of responses on all endpoints should be "403" + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + @skipOnOcV10 @personalSpace + Scenario: send PUT requests to another user's webDav endpoints as normal user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + Then the HTTP status code of responses on all endpoints should be "403" + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "403" + + + Scenario: send PUT requests to webDav endpoints using invalid username but correct password + When user "usero" requests these endpoints with "PUT" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints using invalid username but correct password using the spaces WebDAV API + When user "usero" requests these endpoints with "PUT" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + + Scenario: send PUT requests to webDav endpoints using valid password and username of different user + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints using valid password and username of different user using the spaces WebDAV API + When user "Brian" requests these endpoints with "PUT" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 + Scenario: send PUT requests to webDav endpoints without any authentication + When a user requests these endpoints with "PUT" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @smokeTest @skipOnBruteForceProtection @issue-brute_force_protection-112 @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints without any authentication using the spaces WebDAV API + When a user requests these endpoints with "PUT" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php/dav/spaces/%spaceid%/PARENT | + | /remote.php/dav/spaces/%spaceid%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PUT requests to webDav endpoints using token authentication should not work + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user requests these endpoints with "PUT" using the generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile0.txt | + | /remote.php/webdav/PARENT | + | /remote.php/dav/files/%username%/PARENT | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "401" + + @notToImplementOnOCIS @issue-ocis-reva-37 + Scenario: send PUT requests to webDav endpoints using app password token as password + Given token auth has been enforced + And a new browser session for "Alice" has been started + And the user has generated a new app password named "my-client" + When the user "Alice" requests these endpoints with "PUT" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + | /remote.php/webdav/textfile0.txt | + | /remote.php/dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/PARENT/parent.txt | + Then the HTTP status code of responses on all endpoints should be "204" + When the user "Alice" requests these endpoints with "PUT" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + # this folder is created, so gives 201 - CREATED + | /remote.php/webdav/PARENS | + | /remote.php/dav/files/%username%/FOLDERS | + Then the HTTP status code of responses on all endpoints should be "201" + When the user "Alice" requests these endpoints with "PUT" with body "doesnotmatter" using basic auth and generated app password about user "Alice" + | endpoint | + # this folder already exists so gives 409 - CONFLICT + | /remote.php/dav/files/%username%/FOLDER | + Then the HTTP status code of responses on all endpoints should be "409" diff --git a/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature b/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature new file mode 100644 index 00000000000..466caeaf0a6 --- /dev/null +++ b/tests/acceptance/features/coreApiAuthWebDav/webDavSpecialURLs.feature @@ -0,0 +1,203 @@ +@api @skipOnOcV10.10.0 +Feature: make webdav request with special urls + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + + Scenario: send DELETE requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "DELETE" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "204" + + @skipOnOcV10 @personalSpace + Scenario: send DELETE requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "DELETE" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/dav/spaces/%spaceid%/textfile0.txt | + | //remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "204" + + + Scenario: send GET requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "GET" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "200" + + @skipOnOcV10 @personalSpace + Scenario: send GET requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "GET" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/dav/spaces/%spaceid%/textfile0.txt | + | //remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "200" + + + Scenario: send LOCK requests to webDav endpoints with 2 slashes + When the user "Alice" requests these endpoints with "LOCK" to get property "d:shared" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "200" + + @skipOnOcV10 @personalSpace + Scenario: send LOCK requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When the user "Alice" requests these endpoints with "LOCK" to get property "d:shared" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php/dav/spaces/%spaceid%/textfile0.txt | + | //remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "200" + + + Scenario: send MKCOL requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "MKCOL" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/PARENT1 | + | /remote.php//webdav/PARENT2 | + | //remote.php//webdav/PARENT3 | + | //remote.php/dav//files/%username%/PARENT4 | + | /remote.php/dav/files/%username%//PARENT5 | + | /remote.php/dav//files/%username%/PARENT6 | + Then the HTTP status code of responses on all endpoints should be "201" + + @skipOnOcV10 @personalSpace + Scenario: send MKCOL requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "MKCOL" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/dav/spaces/%spaceid%/PARENT1 | + | /remote.php//dav/spaces/%spaceid%/PARENT2 | + | //remote.php//dav/spaces/%spaceid%/PARENT3 | + | //remote.php/dav//spaces/%spaceid%/PARENT4 | + | /remote.php/dav/spaces/%spaceid%//PARENT5 | + | /remote.php/dav//spaces/%spaceid%/PARENT6 | + Then the HTTP status code of responses on all endpoints should be "201" + + + Scenario: send MOVE requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "MOVE" using password "%regular%" about user "Alice" + | endpoint | destination | + | //remote.php/webdav/textfile0.txt | /remote.php/webdav/textfileZero.txt | + | /remote.php//dav/files/%username%/textfile1.txt | /remote.php/dav/files/%username%/textfileOne.txt | + | /remote.php/webdav//PARENT | /remote.php/webdav/PARENT1 | + | //remote.php/dav/files/%username%//PARENT1 | /remote.php/dav/files/%username%/PARENT2 | + | /remote.php/dav//files/%username%/PARENT2/parent.txt | /remote.php/dav/files/%username%/PARENT2/parent1.txt | + Then the HTTP status code of responses on all endpoints should be "201" + + @skipOnOcV10 @personalSpace + Scenario: send MOVE requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "MOVE" using password "%regular%" about user "Alice" + | endpoint | destination | + | /remote.php//dav/spaces/%spaceid%/textfile1.txt | /remote.php/dav/spaces/%spaceid%/textfileOne.txt | + | /remote.php/dav//spaces/%spaceid%/PARENT | /remote.php/dav/spaces/%spaceid%/PARENT1 | + | //remote.php/dav/spaces/%spaceid%//PARENT1 | /remote.php/dav/spaces/%spaceid%/PARENT2 | + | //remote.php/dav/spaces/%spaceid%/PARENT2/parent.txt | /remote.php/dav/spaces/%spaceid%/PARENT2/parent1.txt | + Then the HTTP status code of responses on all endpoints should be "201" + + + Scenario: send POST requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "500" or "501" + + @skipOnOcV10 @personalSpace + Scenario: send POST requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php//dav/spaces/%spaceid%/textfile1.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "500" or "501" + + + Scenario: send PROPFIND requests to webDav endpoints with 2 slashes + When the user "Alice" requests these endpoints with "PROPFIND" to get property "d:href" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "207" + + @skipOnOcV10 @personalSpace + Scenario: send PROPFIND requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When the user "Alice" requests these endpoints with "PROPFIND" to get property "d:href" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php//dav/spaces/%spaceid%/textfile1.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "207" + + + Scenario: send PROPPATCH requests to webDav endpoints with 2 slashes + When the user "Alice" requests these endpoints with "PROPPATCH" to set property "d:getlastmodified" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php//dav/files/%username%/PARENT/parent.txt | + | /remote.php//webdav/PARENT | + | //remote.php/dav//files/%username%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "207" + + @skipOnOcV10 @personalSpace + Scenario: send PROPPATCH requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When the user "Alice" requests these endpoints with "PROPPATCH" to set property "d:getlastmodified" with password "%regular%" about user "Alice" + | endpoint | + | //remote.php//dav/spaces/%spaceid%/textfile1.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT/parent.txt | + | /remote.php//dav/spaces/%spaceid%/PARENT | + | //remote.php/dav//spaces/%spaceid%//FOLDER | + Then the HTTP status code of responses on all endpoints should be "207" + + + Scenario: send PUT requests to webDav endpoints with 2 slashes + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/webdav/textfile0.txt | + | /remote.php//webdav/textfile1.txt | + | //remote.php//dav/files/%username%/textfile1.txt | + | /remote.php/dav/files/%username%/textfile7.txt | + | //remote.php/dav/files/%username%/PARENT//parent.txt | + Then the HTTP status code of responses on all endpoints should be "204" or "201" + + @skipOnOcV10 @personalSpace + Scenario: send PUT requests to webDav endpoints with 2 slashes using the spaces WebDAV API + When user "Alice" requests these endpoints with "PUT" including body "doesnotmatter" using password "%regular%" about user "Alice" + | endpoint | + | //remote.php/dav/spaces/%spaceid%/textfile0.txt | + | /remote.php//dav/spaces/%spaceid%/textfile1.txt | + | //remote.php//dav/spaces/%spaceid%/textfile1.txt | + | /remote.php/dav/spaces/%spaceid%/textfile7.txt | + | //remote.php/dav/spaces/%spaceid%/PARENT//parent.txt | + Then the HTTP status code of responses on all endpoints should be "204" or "201" diff --git a/tests/acceptance/features/coreApiCapabilities/capabilities.feature b/tests/acceptance/features/coreApiCapabilities/capabilities.feature new file mode 100644 index 00000000000..c34004a8228 --- /dev/null +++ b/tests/acceptance/features/coreApiCapabilities/capabilities.feature @@ -0,0 +1,961 @@ +@api @files_sharing-app-required @issue-ocis-reva-41 +Feature: capabilities + + Background: + Given using OCS API version "1" + + @smokeTest @skipOnOcis + Scenario: Check that the sharing API can be enabled + Given parameter "shareapi_enabled" of app "core" has been set to "no" + And the capabilities setting of "files_sharing" path "api_enabled" has been confirmed to be "" + When the administrator sets parameter "shareapi_enabled" of app "core" to "yes" + Then the capabilities setting of "files_sharing" path "api_enabled" should be "1" + + @smokeTest @skipOnOcis + Scenario: Check that the sharing API can be disabled + Given parameter "shareapi_enabled" of app "core" has been set to "yes" + And the capabilities setting of "files_sharing" path "api_enabled" has been confirmed to be "1" + When the administrator sets parameter "shareapi_enabled" of app "core" to "no" + Then the capabilities setting of "files_sharing" path "api_enabled" should be "" + + @skipOnOcis + Scenario: Check that group sharing can be enabled + Given parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + And the capabilities setting of "files_sharing" path "group_sharing" has been confirmed to be "" + When the administrator sets parameter "shareapi_allow_group_sharing" of app "core" to "yes" + Then the capabilities setting of "files_sharing" path "group_sharing" should be "1" + + @skipOnOcis + Scenario: Check that group sharing can be disabled + Given parameter "shareapi_allow_group_sharing" of app "core" has been set to "yes" + And the capabilities setting of "files_sharing" path "group_sharing" has been confirmed to be "1" + When the administrator sets parameter "shareapi_allow_group_sharing" of app "core" to "no" + Then the capabilities setting of "files_sharing" path "group_sharing" should be "" + + @smokeTest @skipOnOcis + Scenario: getting default capabilities with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | core | status@@@edition | %edition% | + | core | status@@@productname | %productname% | + | core | status@@@version | %version% | + | core | status@@@versionstring | %versionstring% | + | files_sharing | api_enabled | 1 | + | files_sharing | default_permissions | 31 | + | files_sharing | search_min_length | 2 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@multiple | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@supports_upload_only | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@enforced | EMPTY | + | files_sharing | public@@@enforced_for@@@read_only | EMPTY | + | files_sharing | public@@@enforced_for@@@read_write | EMPTY | + | files_sharing | public@@@enforced_for@@@upload_only | EMPTY | + | files_sharing | public@@@enforced_for@@@read_write_delete | EMPTY | + | files_sharing | public@@@expire_date@@@enabled | EMPTY | + | files_sharing | public@@@defaultPublicLinkShareName | Public link | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | share_with_membership_groups_only | EMPTY | + | files_sharing | auto_accept_share | 1 | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files_sharing | user@@@send_mail | EMPTY | + | files | bigfilechunking | 1 | + | files | privateLinks | 1 | + | files | privateLinksDetailsParam | 1 | + + @smokeTest @skipOnOcis + Scenario: getting default capabilities with admin user with new values + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@expire_date@@@enabled | EMPTY | + | files_sharing | group@@@expire_date@@@enabled | EMPTY | + | files_sharing | providers_capabilities@@@ocinternal@@@user@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocinternal@@@group@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocinternal@@@link@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocinternal@@@link@@@element[1] | passwordProtected | + + @smokeTest @skipOnOcis + Scenario: the default capabilities should include share expiration for all of user, group, link and remote (federated) + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@expire_date@@@enabled | EMPTY | + | files_sharing | group@@@expire_date@@@enabled | EMPTY | + | files_sharing | remote@@@expire_date@@@enabled | EMPTY | + | files_sharing | providers_capabilities@@@ocinternal@@@user@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocinternal@@@group@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocinternal@@@link@@@element[0] | shareExpiration | + | files_sharing | providers_capabilities@@@ocFederatedSharing@@@remote@@@element[0] | shareExpiration | + + @smokeTest @skipOnOcis + Scenario: getting new default capabilities in versions after 10.5.0 with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | favorites | 1 | + | files | file_locking_support | 1 | + | files | file_locking_enable_file_action | EMPTY | + + @smokeTest @skipOnOcis + Scenario: lock file action can be enabled + Given parameter "enable_lock_file_action" of app "files" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | file_locking_support | 1 | + | files | file_locking_enable_file_action | 1 | + + @smokeTest @skipOnOcis + Scenario: getting default capabilities with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@profile_picture | 1 | + + @files_trashbin-app-required @skipOnReva + Scenario: getting trashbin app capability with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | undelete | 1 | + + @files_versions-app-required @skipOnReva + Scenario: getting versions app capability with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | versioning | 1 | + + @skipOnOcis + Scenario: getting default_permissions capability with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | default_permissions | 31 | + + @skipOnOcis + Scenario: default_permissions capability can be changed + Given parameter "shareapi_default_permissions" of app "core" has been set to "7" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | default_permissions | 7 | + + @skipOnOcis + Scenario: .htaccess is reported as a blacklisted file by default + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | blacklisted_files@@@element[0] | .htaccess | + + @skipOnOcis + Scenario: multiple files can be reported as blacklisted + Given the administrator has updated system config key "blacklisted_files" with value '["test.txt",".htaccess"]' and type "json" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | blacklisted_files@@@element[0] | test.txt | + | files | blacklisted_files@@@element[1] | .htaccess | + + @skipOnOcis + Scenario: user expire date can be enabled + Given parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@expire_date@@@enabled | 1 | + | files_sharing | user@@@expire_date@@@days | 7 | + | files_sharing | user@@@expire_date@@@enforced | EMPTY | + + @skipOnOcis + Scenario: user expire date can be enforced + Given parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@expire_date@@@enabled | 1 | + | files_sharing | user@@@expire_date@@@days | 7 | + | files_sharing | user@@@expire_date@@@enforced | 1 | + + @skipOnOcis + Scenario: user expire date days can be set + Given parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "14" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | user@@@expire_date@@@enabled | 1 | + | files_sharing | user@@@expire_date@@@days | 14 | + | files_sharing | user@@@expire_date@@@enforced | EMPTY | + + @skipOnOcis + Scenario: group expire date can be enabled + Given parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | group@@@expire_date@@@enabled | 1 | + | files_sharing | group@@@expire_date@@@days | 7 | + | files_sharing | group@@@expire_date@@@enforced | EMPTY | + + @skipOnOcis + Scenario: group expire date can be enforced + Given parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | group@@@expire_date@@@enabled | 1 | + | files_sharing | group@@@expire_date@@@days | 7 | + | files_sharing | group@@@expire_date@@@enforced | 1 | + + @skipOnOcis + Scenario: group expire date days can be set + Given parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "14" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | group@@@expire_date@@@enabled | 1 | + | files_sharing | group@@@expire_date@@@days | 14 | + | files_sharing | group@@@expire_date@@@enforced | EMPTY | + + #feature added in #31824 released in 10.0.10 + @smokeTest @skipOnOcis + Scenario: getting capabilities with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files_sharing | can_share | 1 | + + #feature added in #32414 released in 10.0.10 + @skipOnOcis + Scenario: getting async capabilities when async operations are enabled + Given the administrator has enabled async operations + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | async | | 1.0 | + + + Scenario: getting async capabilities when async operations are disabled + Given the administrator has disabled async operations + When the administrator retrieves the capabilities using the capabilities API + Then the capabilities should contain + | capability | path_to_element | value | + | async | | EMPTY | + + @skipOnOcis + Scenario: Changing public upload + Given parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@multiple | 1 | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Disabling share api + Given parameter "shareapi_enabled" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | EMPTY | + | files_sharing | can_share | EMPTY | + | files_sharing | public@@@enabled | EMPTY | + | files_sharing | public@@@multiple | EMPTY | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | resharing | EMPTY | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Disabling public links + Given parameter "shareapi_allow_links" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | EMPTY | + | files_sharing | public@@@multiple | EMPTY | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing resharing + Given parameter "shareapi_allow_resharing" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | EMPTY | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing federation outgoing + Given parameter "outgoing_server2server_share_enabled" of app "files_sharing" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | EMPTY | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing federation incoming + Given parameter "incoming_server2server_share_enabled" of app "files_sharing" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | EMPTY | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing "password enforced for read-only public link shares" + Given parameter "shareapi_enforce_links_password_read_only" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@password@@@enforced_for@@@read_only | 1 | + | files_sharing | public@@@password@@@enforced_for@@@read_write | EMPTY | + | files_sharing | public@@@password@@@enforced_for@@@upload_only | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing "password enforced for read-write public link shares" + Given parameter "shareapi_enforce_links_password_read_write" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@password@@@enforced_for@@@read_only | EMPTY | + | files_sharing | public@@@password@@@enforced_for@@@read_write | 1 | + | files_sharing | public@@@password@@@enforced_for@@@upload_only | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing "password enforced for write-only public link shares" + Given parameter "shareapi_enforce_links_password_write_only" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@password@@@enforced_for@@@read_only | EMPTY | + | files_sharing | public@@@password@@@enforced_for@@@read_write | EMPTY | + | files_sharing | public@@@password@@@enforced_for@@@upload_only | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing public notifications + Given parameter "shareapi_allow_public_notification" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | 1 | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing public social share + Given parameter "shareapi_allow_social_share" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing expire date + Given parameter "shareapi_default_expire_date" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@expire_date@@@enabled | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing expire date enforcing + Given parameter "shareapi_default_expire_date" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@expire_date@@@enabled | 1 | + | files_sharing | public@@@expire_date@@@enforced | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing group sharing allowed + Given parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | EMPTY | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing only share with group member + Given parameter "shareapi_only_share_with_group_members" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | 1 | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing only share with membership groups + Given parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | share_with_membership_groups_only | 1 | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing auto accept share + Given parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | share_with_membership_groups_only | EMPTY | + | files_sharing | auto_accept_share | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing allow share dialog user enumeration + Given parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" has been set to "no" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing allow share dialog user enumeration for group members only + Given parameter "shareapi_share_dialog_user_enumeration_group_members" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | 1 | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing allow mail notification + Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes" + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files_sharing | user@@@send_mail | 1 | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: Changing exclude groups from sharing + Given parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And group "group1" has been created + And group "hash#group" has been created + And group "group-3" has been created + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["group1","hash#group","group-3"]' + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: When in a group that is excluded from sharing, can_share is off + Given parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And user "Alice" has been created with default attributes and without skeleton files + And group "group1" has been created + And group "hash#group" has been created + And group "group-3" has been created + And group "ordinary-group" has been created + And user "Alice" has been added to group "hash#group" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["group1","hash#group","group-3"]' + When user "Alice" retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | EMPTY | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: When not in any group that is excluded from sharing, can_share is on + Given parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And user "Alice" has been created with default attributes and without skeleton files + And group "group1" has been created + And group "hash#group" has been created + And group "group-3" has been created + And group "ordinary-group" has been created + And user "Alice" has been added to group "ordinary-group" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["group1","hash#group","group-3"]' + When user "Alice" retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: When in a group that is excluded from sharing and in another group, can_share is off + Given parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And user "Alice" has been created with default attributes and without skeleton files + And group "group1" has been created + And group "hash#group" has been created + And group "group-3" has been created + And group "ordinary-group" has been created + And user "Alice" has been added to group "hash#group" + And user "Alice" has been added to group "ordinary-group" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["group1","hash#group","group-3"]' + When user "Alice" retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | can_share | EMPTY | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files | bigfilechunking | 1 | + + @skipOnOcis + Scenario: blacklisted_files_regex is reported in capabilities + When the administrator retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | files | blacklisted_files_regex | \.(part\|filepart)$ | + + @smokeTest + Scenario: getting default capabilities with admin user + When the administrator retrieves the capabilities using the capabilities API + Then the capabilities should contain + | capability | path_to_element | value | + | core | status@@@edition | %edition% | + | core | status@@@product | %productname% | + | core | status@@@productname | %productname% | + | core | status@@@version | %version% | + | core | status@@@versionstring | %versionstring% | + And the version data in the response should contain + | name | value | + | string | %versionstring% | + | edition | %edition% | + | product | %productname% | + And the major-minor-micro version data in the response should match the version string diff --git a/tests/acceptance/features/coreApiCapabilities/capabilitiesWithNormalUser.feature b/tests/acceptance/features/coreApiCapabilities/capabilitiesWithNormalUser.feature new file mode 100644 index 00000000000..55fa7a43840 --- /dev/null +++ b/tests/acceptance/features/coreApiCapabilities/capabilitiesWithNormalUser.feature @@ -0,0 +1,51 @@ +@api @files_sharing-app-required +Feature: default capabilities for normal user + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + # adjust this scenario after fixing tagged issues as its just created to show difference + # in the response items in different environment (core & ocis-reva) + @issue-ocis-reva-175 @issue-ocis-reva-176 + Scenario: getting default capabilities with normal user + When user "Alice" retrieves the capabilities using the capabilities API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the capabilities should contain + | capability | path_to_element | value | + | core | pollinterval | 30000 | + | core | webdav-root | remote.php/webdav | + | core | status@@@edition | %edition% | + | core | status@@@productname | %productname% | + | core | status@@@version | %version% | + | core | status@@@versionstring | %versionstring% | + | files_sharing | api_enabled | 1 | + | files_sharing | default_permissions | 31 | + | files_sharing | search_min_length | 2 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@multiple | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@supports_upload_only | 1 | + | files_sharing | public@@@send_mail | EMPTY | + | files_sharing | public@@@social_share | 1 | + | files_sharing | public@@@enforced | EMPTY | + | files_sharing | public@@@enforced_for@@@read_only | EMPTY | + | files_sharing | public@@@enforced_for@@@read_write | EMPTY | + | files_sharing | public@@@enforced_for@@@upload_only | EMPTY | + | files_sharing | public@@@enforced_for@@@read_write_delete | EMPTY | + | files_sharing | public@@@expire_date@@@enabled | EMPTY | + | files_sharing | public@@@defaultPublicLinkShareName | Public link | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files_sharing | group_sharing | 1 | + | files_sharing | share_with_group_members_only | EMPTY | + | files_sharing | share_with_membership_groups_only | EMPTY | + | files_sharing | auto_accept_share | 1 | + | files_sharing | user_enumeration@@@enabled | 1 | + | files_sharing | user_enumeration@@@group_members_only | EMPTY | + | files_sharing | user@@@send_mail | EMPTY | + | files | bigfilechunking | 1 | + | files | privateLinks | 1 | + | files | privateLinksDetailsParam | 1 | diff --git a/tests/acceptance/features/coreApiFavorites/favorites.feature b/tests/acceptance/features/coreApiFavorites/favorites.feature new file mode 100644 index 00000000000..553c4c1af85 --- /dev/null +++ b/tests/acceptance/features/coreApiFavorites/favorites.feature @@ -0,0 +1,307 @@ +@api +Feature: favorite + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile2.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile3.txt" + And user "Alice" has uploaded file with content "some data" to "/textfile4.txt" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + @issue-ocis-reva-276 + Scenario Outline: Favorite a folder + Given using DAV path + When user "Alice" favorites element "/FOLDER" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" folder "/FOLDER" should be favorited + When user "Alice" gets the following properties of folder "/FOLDER" using the WebDAV API + | propertyName | + | oc:favorite | + Then the HTTP status code should be "207" + And the single response should contain a property "oc:favorite" with value "1" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-276 + Scenario Outline: Unfavorite a folder + Given using DAV path + And user "Alice" has favorited element "/FOLDER" + When user "Alice" unfavorites element "/FOLDER" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" folder "/FOLDER" should not be favorited + When user "Alice" gets the following properties of folder "/FOLDER" using the WebDAV API + | propertyName | + | oc:favorite | + Then the HTTP status code should be "207" + And the single response should contain a property "oc:favorite" with value "0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @issue-ocis-reva-276 + Scenario Outline: Favorite a file + Given using DAV path + When user "Alice" favorites element "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" file "/textfile0.txt" should be favorited + When user "Alice" gets the following properties of file "/textfile0.txt" using the WebDAV API + | propertyName | + | oc:favorite | + Then the HTTP status code should be "207" + And the single response should contain a property "oc:favorite" with value "1" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @issue-ocis-reva-276 + Scenario Outline: Unfavorite a file + Given using DAV path + And user "Alice" has favorited element "/textfile0.txt" + When user "Alice" unfavorites element "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" file "/textfile0.txt" should not be favorited + When user "Alice" gets the following properties of file "/textfile0.txt" using the WebDAV API + | propertyName | + | oc:favorite | + Then the HTTP status code should be "207" + And the single response should contain a property "oc:favorite" with value "0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: Get favorited elements of a folder + Given using DAV path + When user "Alice" favorites element "/FOLDER" using the WebDAV API + And user "Alice" favorites element "/textfile0.txt" using the WebDAV API + And user "Alice" favorites element "/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "207" + And user "Alice" should have favorited the following elements + | /FOLDER | + | /textfile0.txt | + | /textfile1.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get favorited elements of a subfolder + Given using DAV path + And user "Alice" has created folder "/subfolder" + And user "Alice" has uploaded file with content "some data" to "/subfolder/textfile0.txt" + And user "Alice" has uploaded file with content "some data" to "/subfolder/textfile1.txt" + And user "Alice" has uploaded file with content "some data" to "/subfolder/textfile2.txt" + When user "Alice" favorites element "/subfolder/textfile0.txt" using the WebDAV API + And user "Alice" favorites element "/subfolder/textfile1.txt" using the WebDAV API + And user "Alice" favorites element "/subfolder/textfile2.txt" using the WebDAV API + And user "Alice" unfavorites element "/subfolder/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "207" + And user "Alice" should have favorited the following elements + | /subfolder/textfile0.txt | + | /subfolder/textfile2.txt | + And user "Alice" should not have favorited the following elements + | /subfolder/textfile1.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: moving a favorite file out of a share keeps favorite state + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has favorited element "/shared/shared_file.txt" + When user "Brian" moves file "/shared/shared_file.txt" to "/taken_out.txt" using the WebDAV API + Then user "Brian" should have favorited the following elements + | /taken_out.txt | + Examples: + | dav_version | + | old | + | new | + + @issue-33840 @skipOnOcV10 + Scenario Outline: Get favorited elements and limit count of entries + Given using DAV path + And user "Alice" has favorited element "/textfile0.txt" + And user "Alice" has favorited element "/textfile1.txt" + And user "Alice" has favorited element "/textfile2.txt" + And user "Alice" has favorited element "/textfile3.txt" + And user "Alice" has favorited element "/textfile4.txt" + When user "Alice" lists the favorites and limits the result to 3 elements using the WebDAV API + Then the search result should contain any "3" of these entries: + | /textfile0.txt | + | /textfile1.txt | + | /textfile2.txt | + | /textfile3.txt | + | /textfile4.txt | + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-33840 @skipOnOcV10 + Scenario Outline: Get favorited elements paginated in subfolder + Given using DAV path + And user "Alice" has created folder "/subfolder" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile1.txt" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile2.txt" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile3.txt" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile4.txt" + And user "Alice" has copied file "/textfile0.txt" to "/subfolder/textfile5.txt" + And user "Alice" has favorited element "/subfolder/textfile0.txt" + And user "Alice" has favorited element "/subfolder/textfile1.txt" + And user "Alice" has favorited element "/subfolder/textfile2.txt" + And user "Alice" has favorited element "/subfolder/textfile3.txt" + And user "Alice" has favorited element "/subfolder/textfile4.txt" + And user "Alice" has favorited element "/subfolder/textfile5.txt" + When user "Alice" lists the favorites and limits the result to 3 elements using the WebDAV API + Then the search result should contain any "3" of these entries: + | /subfolder/textfile0.txt | + | /subfolder/textfile1.txt | + | /subfolder/textfile2.txt | + | /subfolder/textfile3.txt | + | /subfolder/textfile4.txt | + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: sharer file favorite state should not change the favorite state of sharee + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has moved file "/textfile0.txt" to "/favoriteFile.txt" + And user "Alice" has shared file "/favoriteFile.txt" with user "Brian" + When user "Alice" favorites element "/favoriteFile.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" file "/favoriteFile.txt" should be favorited + And as user "Brian" file "/favoriteFile.txt" should not be favorited + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: sharee file favorite state should not change the favorite state of sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has moved file "/textfile0.txt" to "/favoriteFile.txt" + And user "Alice" has shared file "/favoriteFile.txt" with user "Brian" + When user "Brian" favorites element "/favoriteFile.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Alice" file "/favoriteFile.txt" should not be favorited + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: favoriting a folder does not change the favorite state of elements inside the folder + Given using DAV path + When user "Alice" favorites element "/PARENT/parent.txt" using the WebDAV API + And user "Alice" favorites element "/PARENT" using the WebDAV API + Then the HTTP status code should be "207" + And user "Alice" should have favorited the following elements + | /PARENT | + | /PARENT/parent.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: favorite a file inside of a received share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + When user "Brian" favorites element "/PARENT/parent.txt" using the WebDAV API + Then as user "Brian" file "/PARENT/parent.txt" should be favorited + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: favorite a folder inside of a received share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/PARENT/sub-folder" + And user "Alice" has shared folder "/PARENT" with user "Brian" + When user "Brian" favorites element "/PARENT/sub-folder" using the WebDAV API + Then as user "Brian" folder "/PARENT/sub-folder" should be favorited + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: favorite a received share itself + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + When user "Brian" favorites element "/PARENT" using the WebDAV API + Then as user "Brian" folder "/PARENT" should be favorited + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiFavorites/favoritesSharingToShares.feature b/tests/acceptance/features/coreApiFavorites/favoritesSharingToShares.feature new file mode 100644 index 00000000000..937624f5d50 --- /dev/null +++ b/tests/acceptance/features/coreApiFavorites/favoritesSharingToShares.feature @@ -0,0 +1,83 @@ +@api @files_sharing-app-required +Feature: favorite + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + + + Scenario Outline: favorite a file inside of a received share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" favorites element "/Shares/PARENT/parent.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Brian" file "/Shares/PARENT/parent.txt" should be favorited + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: favorite a folder inside of a received share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/PARENT/sub-folder" + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" favorites element "/Shares/PARENT/sub-folder" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Brian" folder "/Shares/PARENT/sub-folder" should be favorited + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: favorite a received share itself + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" favorites element "/Shares/PARENT" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Brian" folder "/Shares/PARENT" should be favorited + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: moving a favorite file out of a share keeps favorite state + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has favorited element "/Shares/PARENT/parent.txt" + When user "Brian" moves file "/Shares/PARENT/parent.txt" to "/taken_out.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/taken_out.txt" should exist + And as user "Brian" file "/taken_out.txt" should be favorited + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: sharee file favorite state should not change the favorite state of sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/PARENT/parent.txt" with user "Brian" + And user "Brian" has accepted share "/parent.txt" offered by user "Alice" + When user "Brian" favorites element "/Shares/parent.txt" using the WebDAV API + Then the HTTP status code should be "207" + And as user "Brian" file "/Shares/parent.txt" should be favorited + And as user "Alice" file "/PARENT/parent.txt" should not be favorited + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiMain/checksums.feature b/tests/acceptance/features/coreApiMain/checksums.feature new file mode 100644 index 00000000000..89fca532536 --- /dev/null +++ b/tests/acceptance/features/coreApiMain/checksums.feature @@ -0,0 +1,485 @@ +@api +Feature: checksums + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + Scenario Outline: Uploading a file with checksum should work + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" using the WebDAV API + Then the HTTP status code should be "201" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @issue-ocis-reva-196 + Scenario Outline: Uploading a file with checksum should return the checksum in the propfind + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" requests the checksum of "/myChecksumFile.txt" via propfind + Then the webdav checksum should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @issue-ocis-reva-98 + Scenario Outline: Uploading a file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" downloads file "/myChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-196 + Scenario Outline: Moving a file with checksum should return the checksum in the propfind + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" moves file "/myChecksumFile.txt" to "/myMovedChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as user "Alice" the webdav checksum of "/myMovedChecksumFile.txt" via propfind should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-98 + Scenario Outline: Downloading a file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + And user "Alice" has moved file "/myChecksumFile.txt" to "/myMovedChecksumFile.txt" + When user "Alice" downloads file "/myMovedChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-196 + Scenario Outline: Uploading a chunked file with checksum should return the checksum in the propfind + Given using DAV path + And user "Alice" has uploaded chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + And user "Alice" has uploaded chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + And user "Alice" has uploaded chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + When user "Alice" requests the checksum of "/myChecksumFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:acfa6b1565f9710d4d497c6035d5c069bd35a8e8 MD5:45a72715acdd5019c5be30bdbb75233e ADLER32:1ecd03df" + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-17 + Scenario Outline: Uploading a chunked file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has uploaded chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + And user "Alice" has uploaded chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + And user "Alice" has uploaded chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" + When user "Alice" downloads file "/myChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:acfa6b1565f9710d4d497c6035d5c069bd35a8e8" + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @local_storage @files_external-app-required @notToImplementOnOCIS + Scenario Outline: Downloading a file from local storage has correct checksum + Given using DAV path + # Create the file directly in local storage, bypassing ownCloud + And file "prueba_cksum.txt" with text "Test file for checksums" has been created in local storage on the server + # Do a first download, which will trigger ownCloud to calculate a checksum for the file + When user "Alice" downloads file "/local_storage/prueba_cksum.txt" using the WebDAV API + # Now do a download that is expected to have a checksum with it + And user "Alice" downloads file "/local_storage/prueba_cksum.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:a35b7605c8f586d735435535c337adc066c2ccb6" + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-reva-14 + Scenario Outline: Moving file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" moves file "/myChecksumFile.txt" to "/myMovedChecksumFile.txt" using the WebDAV API + And user "Alice" downloads file "/myMovedChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-196 + Scenario Outline: Copying a file with checksum should return the checksum in the propfind using new DAV path + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" copies file "/myChecksumFile.txt" to "/myChecksumFileCopy.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as user "Alice" the webdav checksum of "/myChecksumFileCopy.txt" via propfind should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + Examples: + | dav_version | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-98 + Scenario Outline: Copying file with checksum should return the checksum in the download header using new DAV path + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + When user "Alice" copies file "/myChecksumFile.txt" to "/myChecksumFileCopy.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the header checksum when user "Alice" downloads file "/myChecksumFileCopy.txt" using the WebDAV API should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f" + Examples: + | dav_version | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @issue-ocis-reva-196 + Scenario Outline: Sharing a file with checksum should return the checksum in the propfind using new DAV path + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + And user "Alice" has shared file "/myChecksumFile.txt" with user "Brian" + And user "Brian" has accepted share "/myChecksumFile.txt" offered by user "Alice" + When user "Brian" requests the checksum of "/Shares/myChecksumFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + Examples: + | dav_version | + | new | + + @files_sharing-app-required @issue-ocis-reva-196 @skipOnOcV10 + Scenario Outline: Modifying a shared file should return correct checksum in the propfind using new DAV path + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + And user "Alice" has shared file "/myChecksumFile.txt" with user "Brian" + And user "Brian" has accepted share "/myChecksumFile.txt" offered by user "Alice" + When user "Brian" uploads file with checksum "SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399" and content "Some Text" to "/Shares/myChecksumFile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the webdav checksum of "/myChecksumFile.txt" via propfind should match "" + Examples: + | dav_version | checksum | + | new | SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399 MD5:56e57920c3c8c727bfe7a5288cdf61c4 ADLER32:1048035a | + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload new DAV chunked file where checksum matches + Given using new DAV path + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with checksum "SHA1:5d84d61b03fdacf813640f5242d309721e0629b1" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload new DAV chunked file where checksum does not match + Given using new DAV path + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with checksum "SHA1:f005ba11" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 400" respectively + And user "Alice" should not see the following elements + | /myChunkedFile.txt | + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload new DAV chunked file using async MOVE where checksum matches + Given using new DAV path + And the administrator has enabled async operations + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with checksum "SHA1:5d84d61b03fdacf813640f5242d309721e0629b1" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 202" respectively + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "BBBBBCCCCC" + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload new DAV chunked file using async MOVE where checksum does not match + Given using new DAV path + And the administrator has enabled async operations + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with checksum "SHA1:f005ba11" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 202" respectively + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^error$/ | + | errorCode | /^400$/ | + | errorMessage | /^The computed checksum does not match the one received from the client.$/ | + And user "Alice" should not see the following elements + | /myChunkedFile.txt | + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload new DAV chunked file using async MOVE where checksum does not match - retry with correct checksum + Given using new DAV path + And the administrator has enabled async operations + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with checksum "SHA1:f005ba11" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with checksum "SHA1:5d84d61b03fdacf813640f5242d309721e0629b1" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 202, 202" respectively + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "BBBBBCCCCC" + + @issue-ocis-reva-99 + Scenario Outline: Upload a file where checksum does not match + Given using DAV path + When user "Alice" uploads file with checksum "SHA1:f005ba11" and content "Some Text" to "/chksumtst.txt" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should not see the following elements + | /chksumtst.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Upload a file where checksum does match + Given using DAV path + When user "Alice" uploads file with checksum "SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399" and content "Some Text" to "/chksumtst.txt" using the WebDAV API + Then the HTTP status code should be "201" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-99 + Scenario Outline: Uploaded file should have the same checksum when downloaded + Given using DAV path + And user "Alice" has uploaded file with checksum "SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399" and content "Some Text" to "/chksumtst.txt" + When user "Alice" downloads file "/chksumtst.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | OC-Checksum | SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @local_storage @files_external-app-required @notToImplementOnOCIS @skipOnEncryptionType:user-keys @encryption-issue-42 + Scenario Outline: Uploaded file to external storage should have the same checksum when downloaded + Given using DAV path + And user "Alice" has uploaded file with checksum "SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399" and content "Some Text" to "/local_storage/chksumtst.txt" + When user "Alice" downloads file "/local_storage/chksumtst.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | OC-Checksum | SHA1:ce5582148c6f0c1282335b87df5ed4be4b781399 | + Examples: + | dav_version | + | old | + | new | + + ## Validation Plugin or Old Endpoint Specific + @issue-ocis-reva-17 + Scenario Outline: Uploading an old method chunked file with checksum should fail using new DAV path + Given using DAV path + When user "Alice" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:45a72715acdd5019c5be30bdbb75233e" using the WebDAV API + Then the HTTP status code should be "503" + And user "Alice" should not see the following elements + | /myChecksumFile.txt | + Examples: + | dav_version | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + ## upload overwriting + @issue-ocis-reva-196 + Scenario Outline: Uploading a file with MD5 checksum overwriting an existing file + Given using DAV path + And user "Alice" has uploaded file with content "some data" to "textfile0.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile0.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the webdav checksum of "/textfile0.txt" via propfind should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + And the content of file "/textfile0.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-196 + Scenario Outline: Uploading a file with SHA1 checksum overwriting an existing file + Given using DAV path + And user "Alice" has uploaded file with content "some data" to "textfile0.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile0.txt" with checksum "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the webdav checksum of "/textfile0.txt" via propfind should match "SHA1:3ee962b839762adb0ad8ba6023a4690be478de6f MD5:d70b40f177b14b470d1756a3c12b963a ADLER32:8ae90960" + And the content of file "/textfile0.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-224 @issue-ocis-reva-196 + Scenario Outline: Uploading a file with invalid SHA1 checksum overwriting an existing file + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile0.txt" with checksum "SHA1:f005ba11f005ba11f005ba11f005ba11f005ba11" using the WebDAV API + Then the HTTP status code should be "400" + And as user "Alice" the webdav checksum of "/textfile0.txt" via propfind should match "SHA1:2052377dec0724bda0d57aeab67fa819278b7f74 MD5:096e350e9ff1339a997a14145f9fc4b9 ADLER32:7d5a0921" + And the content of file "/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload overwriting a file with new chunking and correct checksum + Given using new DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/textfile0.txt" with checksum "SHA1:5d84d61b03fdacf813640f5242d309721e0629b1" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 204" respectively + And the content of file "/textfile0.txt" for user "Alice" should be "BBBBBCCCCC" + + @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-224 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload overwriting a file with new chunking and invalid checksum + Given using new DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has created a new chunking upload with id "chunking-42" + When user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/textfile0.txt" with checksum "SHA1:f005ba11" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 201, 400" respectively + And the content of file "/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + + @issue-ocis-reva-214 + Scenario Outline: Uploading a file with checksum should work for file with special characters + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav_version | renamed_file | + | old | " oc?test=ab&cd " | + | old | "# %ab ab?=ed" | + | new | " oc?test=ab&cd " | + | new | "# %ab ab?=ed" | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | renamed_file | + | spaces | " oc?test=ab&cd " | + | spaces | "# %ab ab?=ed" | diff --git a/tests/acceptance/features/coreApiMain/main.feature b/tests/acceptance/features/coreApiMain/main.feature new file mode 100644 index 00000000000..47be8bbc0ff --- /dev/null +++ b/tests/acceptance/features/coreApiMain/main.feature @@ -0,0 +1,13 @@ +@api +Feature: Other tests related to api + + @issue-ocis-reva-100 + Scenario: robots.txt file should be accessible + When a user requests "/robots.txt" with "GET" and no authentication + Then the HTTP status code should be "200" + And the content in the response should match the following content: + """ + User-agent: * + Disallow: / + + """ diff --git a/tests/acceptance/features/coreApiMain/status.feature b/tests/acceptance/features/coreApiMain/status.feature new file mode 100644 index 00000000000..f24c8b9761c --- /dev/null +++ b/tests/acceptance/features/coreApiMain/status.feature @@ -0,0 +1,10 @@ +@api +Feature: Status + + @smokeTest + Scenario: Status.php is correct + When the administrator requests status.php + Then the status.php response should include + """ + {"installed":true,"maintenance":false,"needsDbUpgrade":false,"version":"$CURRENT_VERSION","versionstring":"$CURRENT_VERSION_STRING","edition":"$EDITION","productname":"$PRODUCTNAME","product":"$PRODUCT"} + """ diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature new file mode 100644 index 00000000000..315d3280467 --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareExpirationDate.feature @@ -0,0 +1,757 @@ +@api @files_sharing-app-required @issue-ocis-1328 @issue-ocis-1250 +Feature: a default expiration date can be specified for shares with users or groups + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for users, user shares without specifying expireDate + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has created folder "/FOLDER" + When user "Alice" shares folder "/FOLDER" with user "Brian" using the sharing API + And user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "" + And the fields of the last response to user "Alice" should include + | expiration | | + And the response when user "Brian" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for users, user shares with expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +15 days | + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | expiration | +15 days | + | share_with | %username% | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date not enabled, user shares with expiration date set + Given using OCS API version "" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +15 days | + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | expiration | +15 days | + | share_with | %username% | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for users, user shares with expiration date and then disables + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +15 days | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When the administrator sets parameter "shareapi_default_expire_date_user_share" of app "core" to "no" + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | expiration | +15 days | + | share_with | %username% | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users, user shares with expiration date and then disables + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When the administrator sets parameter "shareapi_default_expire_date_user_share" of app "core" to "no" + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | share_with | %username% | + | expiration | +7 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +7 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for groups, user shares without specifying expireDate + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with group "grp1" + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And the fields of the last response to user "Alice" should include + | expiration | | + And the response when user "Brian" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for groups, user shares with expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +15 days | + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | expiration | +15 days | + | share_with | grp1 | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date not enabled for groups, user shares with expiration date set + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +15 days | + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | expiration | +15 days | + | share_with | grp1 | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled but not enforced for groups, user shares with expiration date and then disables + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +15 days | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When the administrator sets parameter "shareapi_default_expire_date_group_share" of app "core" to "no" + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | share_with | grp1 | + | expiration | +15 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +15 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for groups, user shares with expiration date and then disables + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | /FOLDER | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +3 days | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When the administrator sets parameter "shareapi_default_expire_date_group_share" of app "core" to "no" + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/FOLDER | + | uid_owner | %username% | + | share_with | grp1 | + | expiration | +3 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +3 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users, user shares without setting expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/textfile0.txt | + | uid_owner | %username% | + | share_with | %username% | + | expiration | +7 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +7 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users, user shares with expiration date more than the default + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +10 days | + Then the HTTP status code should be "" + And the OCS status code should be "404" + And the OCS status message should be "Cannot set expiration date more than 7 days in the future" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users/max expire date is set, user shares without setting expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | user | + | file_target | /Shares/textfile0.txt | + | uid_owner | %username% | + | share_with | %username% | + | expiration | +30 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users/max expire date set, user shares with expiration date more than the max expire date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +40 days | + Then the HTTP status code should be "" + And the OCS status code should be "404" + And the OCS status message should be "Cannot set expiration date more than 30 days in the future" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for users/max expire date is set, user shares and changes the max expire date greater than the previous one + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" with permissions "read,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When the administrator sets parameter "shareapi_expire_after_n_days_user_share" of app "core" to "40" + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled for users/max expire date is set, user shares and changes max expire date less than the previous one + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" with permissions "read,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When the administrator sets parameter "shareapi_expire_after_n_days_user_share" of app "core" to "15" + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for groups, user shares without setting expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/textfile0.txt | + | uid_owner | %username% | + | share_with | grp1 | + | expiration | +7 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +7 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for groups, user shares with expiration date more than the default + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +10 days | + Then the HTTP status code should be "" + And the OCS status code should be "404" + And the OCS status message should be "Cannot set expiration date more than 7 days in the future" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for groups/max expire date is set, user shares without setting expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | share_type | group | + | file_target | /Shares/textfile0.txt | + | uid_owner | %username% | + | share_with | grp1 | + | expiration | +30 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled and enforced for groups/max expire date set, user shares with expiration date more than the max expire date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | group | + | shareWith | grp1 | + | permissions | read,share | + | expireDate | +40 days | + Then the HTTP status code should be "" + And the OCS status code should be "404" + And the OCS status message should be "Cannot set expiration date more than 30 days in the future" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: sharing with default expiration date enabled for groups/max expire date is set, user shares and changes the max expire date greater than the previous one + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" with permissions "read,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When the administrator sets parameter "shareapi_expire_after_n_days_group_share" of app "core" to "40" + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | +30 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enabled for groups/max expire date is set, user shares and changes max expire date less than the previous one + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" with permissions "read,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When the administrator sets parameter "shareapi_expire_after_n_days_group_share" of app "core" to "15" + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | +30 days | + And the response when user "Brian" gets the info of the last share should include + | expiration | +30 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enforced for users, user shares to a group without setting an expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has shared folder "FOLDER" with group "grp1" with permissions "read,share" + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | | + And the response when user "Brian" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enforced for groups, user shares to another user + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And user "Alice" has created folder "FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read,share" + When user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the info about the last share by user "Alice" with user "Brian" should include + | expiration | | + And the response when user "Brian" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: sharing with default expiration date enforced for users, user shares with invalid expiration date set + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | INVALID-DATE | + Then the HTTP status code should be "" + And the OCS status code should be "" + And the OCS status message should be "Invalid date, date format must be YYYY-MM-DD" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 404 | 200 | + | 2 | 404 | 404 | + + + Scenario Outline: sharing with default expiration date enforced for users, user shares with different time format + Given using OCS API version "2" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "200" + And the fields of the last response to user "Alice" should include + | expiration | 2050-12-11 | + And the response when user "Brian" gets the info of the last share should include + | expiration | 2050-12-11 | + Examples: + | date | + | 2050-12-11 | + | 11-12-2050 | + | 12/11/2050 | + | 11.12.2050 | + | 11.12.2050 12:30:40 | + + + Scenario Outline: user shares with humanized expiration date format + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the fields of the last response to user "Alice" should include + | expiration | | + And the response when user "Brian" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | expiration_date | default | enforce | + | 1 | today | yes | yes | + | 2 | today | yes | yes | + | 1 | tomorrow | yes | yes | + | 2 | tomorrow | yes | yes | + | 1 | today | yes | no | + | 2 | today | yes | no | + | 1 | tomorrow | yes | no | + | 2 | tomorrow | yes | no | + | 1 | today | no | no | + | 2 | today | no | no | + | 1 | tomorrow | no | no | + | 2 | tomorrow | no | no | + + + Scenario Outline: user shares with humanized expiration date format in past + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | yesterday | + Then the HTTP status code should be "" + And the OCS status code should be "" + And the OCS status message should be "Expiration date is in the past" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | ocs_status_code | http_status_code | default | enforce | + | 1 | 404 | 200 | yes | yes | + | 2 | 404 | 404 | yes | yes | + | 1 | 404 | 200 | yes | no | + | 2 | 404 | 404 | yes | no | + | 1 | 404 | 200 | no | no | + | 2 | 404 | 404 | no | no | + + + Scenario Outline: user shares with invalid humanized expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | 123 | + Then the HTTP status code should be "" + And the OCS status code should be "" + And the OCS status message should be "Invalid date, date format must be YYYY-MM-DD" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | ocs_status_code | http_status_code | default | enforce | + | 1 | 404 | 200 | yes | yes | + | 2 | 404 | 404 | yes | yes | + | 1 | 404 | 200 | yes | no | + | 2 | 404 | 404 | yes | no | + | 1 | 404 | 200 | no | no | + | 2 | 404 | 404 | no | no | + + + Scenario Outline: sharing with default expiration date enforced for users, user shares with past expiration date set + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDateAsString | -10 days | + Then the HTTP status code should be "" + And the OCS status code should be "" + And the OCS status message should be "Expiration date is in the past" + And the sharing API should report to user "Brian" that no shares are in the pending state + And user "Brian" should not have any received shares + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 404 | 200 | + | 2 | 404 | 404 | + + @issue-36569 + Scenario Outline: sharing with default expiration date enforced for users, max expire date is 0, user shares without specifying expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "0" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the fields of the last response to user "Alice" should include + | expiration | today | + And the response when user "Brian" gets the info of the last share should include + | expiration | today | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: sharing with default expiration date enforced for users, max expire date is 1, user shares without specifying expiration date + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the fields of the last response to user "Alice" should include + | expiration | tomorrow | + And the response when user "Brian" gets the info of the last share should include + | expiration | tomorrow | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareResourceCaseSensitiveName.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareResourceCaseSensitiveName.feature new file mode 100644 index 00000000000..19963c6ecd7 --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareResourceCaseSensitiveName.feature @@ -0,0 +1,298 @@ +@api @files_sharing-app-required +Feature: Sharing resources with different case names with the sharee and checking the coexistence of resources on sharee/receivers side + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario: sharing files with different case names with an internal user + Given user "Alice" has uploaded the following files with content "some data" + | path | + | textfile.txt | + | text_file.txt | + | 123textfile.txt | + | textfile.XYZ.txt | + | TEXTFILE.txt | + | TEXT_FILE.txt | + | 123TEXTFILE.txt | + | TEXTFILE.xyz.txt | + When user "Alice" shares the following files with user "Brian" using the sharing API + | path | + | textfile.txt | + | text_file.txt | + | 123textfile.txt | + | textfile.XYZ.txt | + | TEXTFILE.txt | + | TEXT_FILE.txt | + | 123TEXTFILE.txt | + | TEXTFILE.xyz.txt | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /textfile.txt | + | /text_file.txt | + | /123textfile.txt | + | /textfile.XYZ.txt | + | /TEXTFILE.txt | + | /TEXT_FILE.txt | + | /123TEXTFILE.txt | + | /TEXTFILE.xyz.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following files should exist + | path | + | /Shares/textfile.txt | + | /Shares/text_file.txt | + | /Shares/123textfile.txt | + | /Shares/textfile.XYZ.txt | + | /Shares/TEXTFILE.txt | + | /Shares/TEXT_FILE.txt | + | /Shares/123TEXTFILE.txt | + | /Shares/TEXTFILE.xyz.txt | + + + Scenario: sharing folders with different case names with an internal user + Given user "Alice" has created the following folders + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + When user "Alice" shares the following folders with user "Brian" using the sharing API + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following folders should exist + | path | + | /Shares/FO | + | /Shares/F_O | + | /Shares/123FO | + | /Shares/FO.XYZ | + | /Shares/fo | + | /Shares/f_o | + | /Shares/123fo | + | /Shares/fo.xyz | + + + Scenario: sharing files and folders with different case names with an internal user + Given user "Alice" has uploaded the following files with content "some data" + | path | + | casesensitive.txt | + | case_sensitive.txt | + | 123CASE_SENSITIVE.txt | + | casesensitive.xyz.txt | + And user "Alice" has created the following folders + | path | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + When user "Alice" shares the following files with user "Brian" using the sharing API + | path | + | casesensitive.txt | + | case_sensitive.txt | + | 123CASE_SENSITIVE.txt | + | casesensitive.xyz.txt | + And user "Alice" shares the following folders with user "Brian" using the sharing API + | path | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /casesensitive.txt | + | /case_sensitive.txt | + | /123CASE_SENSITIVE.txt | + | /casesensitive.xyz.txt | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following files should exist + | path | + | /Shares/casesensitive.txt | + | /Shares/case_sensitive.txt | + | /Shares/123CASE_SENSITIVE.txt | + | /Shares/casesensitive.xyz.txt | + And as "Brian" the following folders should exist + | path | + | /Shares/CASESENSITIVE | + | /Shares/CASE_SENSITIVE | + | /Shares/123case_sensitive | + | /Shares/CASESENSITIVE.xyz | + + + Scenario: sharing files with different case names with group members + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded the following files with content "some data" + | path | + | textfile.txt | + | text_file.txt | + | 123textfile.txt | + | textfile.XYZ.txt | + | TEXTFILE.txt | + | TEXT_FILE.txt | + | 123TEXTFILE.txt | + | TEXTFILE.xyz.txt | + When user "Alice" shares the following files with group "grp1" using the sharing API + | path | + | textfile.txt | + | text_file.txt | + | 123textfile.txt | + | textfile.XYZ.txt | + | TEXTFILE.txt | + | TEXT_FILE.txt | + | 123TEXTFILE.txt | + | TEXTFILE.xyz.txt | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /textfile.txt | + | /text_file.txt | + | /123textfile.txt | + | /textfile.XYZ.txt | + | /TEXTFILE.txt | + | /TEXT_FILE.txt | + | /123TEXTFILE.txt | + | /TEXTFILE.xyz.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following files should exist + | path | + | /Shares/textfile.txt | + | /Shares/text_file.txt | + | /Shares/123textfile.txt | + | /Shares/textfile.XYZ.txt | + | /Shares/TEXTFILE.txt | + | /Shares/TEXT_FILE.txt | + | /Shares/123TEXTFILE.txt | + | /Shares/TEXTFILE.xyz.txt | + + + Scenario: sharing folders with different case names with group members + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created the following folders + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + When user "Alice" shares the following folders with group "grp1" using the sharing API + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /FO | + | /F_O | + | /123FO | + | /FO.XYZ | + | /fo | + | /f_o | + | /123fo | + | /fo.xyz | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following folders should exist + | path | + | /Shares/FO | + | /Shares/F_O | + | /Shares/123FO | + | /Shares/FO.XYZ | + | /Shares/fo | + | /Shares/f_o | + | /Shares/123fo | + | /Shares/fo.xyz | + + + Scenario: sharing files and folders with different case names with group members + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded the following files with content "some data" + | path | + | casesensitive.txt | + | case_sensitive.txt | + | 123CASE_SENSITIVE.txt | + | casesensitive.xyz.txt | + And user "Alice" has created the following folders + | path | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + When user "Alice" shares the following files with group "grp1" using the sharing API + | path | + | casesensitive.txt | + | case_sensitive.txt | + | 123CASE_SENSITIVE.txt | + | casesensitive.xyz.txt | + And user "Alice" shares the following folders with group "grp1" using the sharing API + | path | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /casesensitive.txt | + | /case_sensitive.txt | + | /123CASE_SENSITIVE.txt | + | /casesensitive.xyz.txt | + | /CASESENSITIVE | + | /CASE_SENSITIVE | + | /123case_sensitive | + | /CASESENSITIVE.xyz | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" the following files should exist + | path | + | /Shares/casesensitive.txt | + | /Shares/case_sensitive.txt | + | /Shares/123CASE_SENSITIVE.txt | + | /Shares/casesensitive.xyz.txt | + And as "Brian" the following folders should exist + | path | + | /Shares/CASESENSITIVE | + | /Shares/CASE_SENSITIVE | + | /Shares/123case_sensitive | + | /Shares/CASESENSITIVE.xyz | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature new file mode 100644 index 00000000000..15739b7042f --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature @@ -0,0 +1,34 @@ +@api @files_sharing-app-required +Feature: resources shared with the same name are received with unique names + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + + @smokeTest @issue-ocis-2131 + Scenario Outline: unique target names for incoming shares + Given user "Alice" has created folder "/foo" + And user "Brian" has created folder "/foo" + When user "Alice" shares folder "/foo" with user "Carol" using the sharing API + And user "Carol" accepts share "/foo" offered by user "Alice" using the sharing API + And user "Brian" shares folder "/foo" with user "Carol" using the sharing API + And user "Carol" accepts share "/foo" offered by user "Brian" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should see the following elements + | Shares/foo/ | + | | + @skipOnOcis + Examples: + | share | + | /Shares/foo (2)/ | + @skipOnOcV10 + Examples: + | share | + | /Shares/foo (1)/ | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature new file mode 100644 index 00000000000..3b8da45500c --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature @@ -0,0 +1,83 @@ +@api @files_sharing-app-required @issue-ocis-reva-41 @skipOnOcis +Feature: cannot share resources when in a group that is excluded from sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + + + Scenario Outline: user who is excluded from sharing tries to share a file with another user + Given using OCS API version "" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["grp1"]' + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/fileToShare.txt" + When user "Brian" shares file "fileToShare.txt" with user "Alice" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the sharing API should report to user "Alice" that no shares are in the pending state + And as "Alice" file "Shares/fileToShare.txt" should not exist + And as "Alice" file "fileToShare.txt" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + + Scenario Outline: user who is excluded from sharing tries to share a file with a group + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp2" has been created + And user "Carol" has been added to group "grp2" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["grp1"]' + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/fileToShare.txt" + When user "Brian" shares file "fileToShare.txt" with group "grp2" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the sharing API should report to user "Carol" that no shares are in the pending state + And as "Carol" file "Shares/fileToShare.txt" should not exist + And as "Carol" file "fileToShare.txt" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + + Scenario Outline: user who is excluded from sharing tries to share a folder with another user + Given using OCS API version "" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["grp1"]' + And user "Brian" has created folder "folderToShare" + When user "Brian" shares folder "folderToShare" with user "Alice" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the sharing API should report to user "Alice" that no shares are in the pending state + And as "Alice" folder "Shares/folderToShare" should not exist + And as "Alice" folder "folderToShare" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + + Scenario Outline: user who is excluded from sharing tries to share a folder with a group + Given using OCS API version "" + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["grp0"]' + And user "Alice" has created folder "folderToShare" + When user "Alice" shares folder "folderToShare" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the sharing API should report to user "Brian" that no shares are in the pending state + And as "Brian" folder "Shares/folderToShare" should not exist + And as "Brian" folder "folderToShare" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature new file mode 100644 index 00000000000..6080180b09a --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareDefaultFolderForReceivedShares.feature @@ -0,0 +1,23 @@ +@api @files_sharing-app-required @issue-ocis-1327 +Feature: shares are received in the default folder for received shares + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Do not allow sharing of the entire share_folder + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + When user "Alice" shares folder "/FOLDER" with user "Brian" using the sharing API + And user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + And user "Brian" unshares folder "Shares/FOLDER" using the WebDAV API + And user "Brian" shares folder "/Shares" with user "Alice" using the sharing API + Then the OCS status code of responses on each endpoint should be "" respectively + And the HTTP status code of responses on each endpoint should be "" respectively + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100, 100, 404 | 200, 200, 204, 200 | + | 2 | 200, 200, 404 | 200, 200, 204, 404 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupAndUserWithSameName.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupAndUserWithSameName.feature new file mode 100644 index 00000000000..60d20827b8d --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupAndUserWithSameName.feature @@ -0,0 +1,95 @@ +@api @files_sharing-app-required +Feature: sharing works when a username and group name are the same + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + @skipOnLDAP + Scenario: creating a new share with user and a group having same name + Given these users have been created without skeleton files: + | username | + | Brian | + | Carol | + And group "Brian" has been created + And user "Carol" has been added to group "Brian" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has shared file "randomfile.txt" with group "Brian" + And user "Carol" has accepted share "/randomfile.txt" offered by user "Alice" + When user "Alice" shares file "randomfile.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /Shares/randomfile.txt | + And user "Carol" should see the following elements + | /Shares/randomfile.txt | + And the content of file "/Shares/randomfile.txt" for user "Brian" should be "Random data" + And the content of file "/Shares/randomfile.txt" for user "Carol" should be "Random data" + + @skipOnLDAP + Scenario: creating a new share with group and a user having same name + Given these users have been created without skeleton files: + | username | + | Brian | + | Carol | + And group "Brian" has been created + And user "Carol" has been added to group "Brian" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has shared file "randomfile.txt" with user "Brian" + And user "Brian" has accepted share "/randomfile.txt" offered by user "Alice" + When user "Alice" shares file "randomfile.txt" with group "Brian" using the sharing API + And user "Carol" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /Shares/randomfile.txt | + And user "Carol" should see the following elements + | /Shares/randomfile.txt | + And the content of file "/Shares/randomfile.txt" for user "Brian" should be "Random data" + And the content of file "/Shares/randomfile.txt" for user "Carol" should be "Random data" + + @skipOnLDAP + Scenario: creating a new share with user and a group having same name but different case + Given these users have been created without skeleton files: + | username | + | Brian | + | Carol | + And group "brian" has been created + And user "Carol" has been added to group "brian" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has shared file "randomfile.txt" with group "brian" + And user "Carol" has accepted share "/randomfile.txt" offered by user "Alice" + When user "Alice" shares file "randomfile.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /Shares/randomfile.txt | + And user "Carol" should see the following elements + | /Shares/randomfile.txt | + And the content of file "/Shares/randomfile.txt" for user "Brian" should be "Random data" + And the content of file "/Shares/randomfile.txt" for user "Carol" should be "Random data" + + @skipOnLDAP + Scenario: creating a new share with group and a user having same name but different case + Given these users have been created without skeleton files: + | username | + | Brian | + | Carol | + And group "brian" has been created + And user "Carol" has been added to group "brian" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has shared file "randomfile.txt" with user "Brian" + And user "Brian" has accepted share "/randomfile.txt" offered by user "Alice" + When user "Alice" shares file "randomfile.txt" with group "brian" using the sharing API + And user "Carol" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should see the following elements + | /Shares/randomfile.txt | + And user "Brian" should see the following elements + | /Shares/randomfile.txt | + And the content of file "/Shares/randomfile.txt" for user "Carol" should be "Random data" + And the content of file "/Shares/randomfile.txt" for user "Brian" should be "Random data" diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature new file mode 100644 index 00000000000..08ddb5a53ad --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature @@ -0,0 +1,90 @@ +@api @files_sharing-app-required @notToImplementOnOCIS +Feature: share with groups, group names are case-sensitive + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "/textfile1.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 2" to "/textfile2.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 3" to "/textfile3.txt" + + @skipOnLDAP @issue-ldap-250 + Scenario Outline: group names are case-sensitive, sharing with groups with different upper and lower case names + Given using OCS API version "" + And group "" has been created + And group "" has been created + And group "" has been created + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + | David | + And user "Brian" has been added to group "" + And user "Carol" has been added to group "" + And user "David" has been added to group "" + When user "Alice" shares file "textfile1.txt" with group "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Brian" accepts share "/textfile1.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/textfile1.txt" for user "Brian" should be "ownCloud test text file 1" + When user "Alice" shares file "textfile2.txt" with group "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Carol" accepts share "/textfile2.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/textfile2.txt" for user "Carol" should be "ownCloud test text file 2" + When user "Alice" shares file "textfile3.txt" with group "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "David" accepts share "/textfile3.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/textfile3.txt" for user "David" should be "ownCloud test text file 3" + Examples: + | ocs_api_version | group_id1 | group_id2 | group_id3 | ocs_status_code | + | 1 | case-sensitive-group | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | 100 | + | 1 | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | case-sensitive-group | 100 | + | 1 | CASE-SENSITIVE-GROUP | case-sensitive-group | Case-Sensitive-Group | 100 | + | 2 | case-sensitive-group | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | 200 | + | 2 | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | case-sensitive-group | 200 | + | 2 | CASE-SENSITIVE-GROUP | case-sensitive-group | Case-Sensitive-Group | 200 | + + @skipOnLDAP @issue-ldap-250 + Scenario Outline: group names are case-sensitive, sharing with nonexistent groups with different upper and lower case names + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + And group "" has been created + And user "Brian" has been added to group "" + When user "Alice" shares file "textfile1.txt" with group "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Brian" accepts share "/textfile1.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | share_with | | + | file_target | /Shares/textfile1.txt | + | path | /Shares/textfile1.txt | + | permissions | share,read,update | + | uid_owner | %username% | + And the content of file "/Shares/textfile1.txt" for user "Brian" should be "ownCloud test text file 1" + When user "Alice" shares file "textfile2.txt" with group "" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + When user "Alice" shares file "textfile3.txt" with group "" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | group_id1 | group_id2 | group_id3 | ocs_status_code | http_status_code | + | 1 | case-sensitive-group | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | 100 | 200 | + | 1 | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | case-sensitive-group | 100 | 200 | + | 1 | CASE-SENSITIVE-GROUP | case-sensitive-group | Case-Sensitive-Group | 100 | 200 | + | 2 | case-sensitive-group | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | 200 | 404 | + | 2 | Case-Sensitive-Group | CASE-SENSITIVE-GROUP | case-sensitive-group | 200 | 404 | + | 2 | CASE-SENSITIVE-GROUP | case-sensitive-group | Case-Sensitive-Group | 200 | 404 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature new file mode 100644 index 00000000000..0c98008bdfd --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature @@ -0,0 +1,624 @@ +@api @files_sharing-app-required +Feature: share resources where the sharee receives the share in multiple ways + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: creating and accepting a new share with user who already received a share through their group + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" shares file "/textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/textfile0.txt" offered by user "Alice" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | | + | path | /Shares/textfile0 (2).txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + @skipOnOcis + Examples: + | ocs_api_version | ocs_status_code | file_target | + | 1 | 100 | /Shares/textfile0 (2).txt | + | 2 | 200 | /Shares/textfile0 (2).txt | + + @skipOnOcV10 @issue-2131 + Examples: + | ocs_api_version | ocs_status_code | file_target | + | 1 | 100 | /textfile0 (2).txt | + | 2 | 200 | /textfile0 (2).txt | + + @issue-ocis-1289 + Scenario Outline: Share of folder and sub-folder to same user + Given using OCS API version "" + And group "grp4" has been created + And user "Brian" has been added to group "grp4" + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/parent.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/CHILD/child.txt" + When user "Alice" shares folder "/PARENT" with user "Brian" using the sharing API + And user "Alice" shares folder "/PARENT/CHILD" with group "grp4" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/PARENT" offered by user "Alice" + And user "Brian" should be able to accept pending share "" offered by user "Alice" + And user "Brian" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/CHILD/ | + | /Shares/CHILD/child.txt | + Examples: + | ocs_api_version | ocs_status_code | pending_sub_share_path | + | 1 | 100 | /CHILD | + | 2 | 200 | /CHILD | + + @issue-ocis-2021 + Scenario Outline: sharing subfolder when parent already shared + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with group "grp1" + When user "Alice" shares folder "/test/sub" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "" offered by user "Alice" + And as "Brian" folder "/Shares/sub" should exist + Examples: + | ocs_api_version | ocs_status_code | pending_share_path | + | 1 | 100 | /sub | + | 2 | 200 | /sub | + + @issue-ocis-2021 + Scenario Outline: sharing subfolder when parent already shared with group of sharer + Given using OCS API version "" + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with group "grp0" + When user "Alice" shares folder "/test/sub" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "" offered by user "Alice" + And as "Brian" folder "/Shares/sub" should exist + Examples: + | ocs_api_version | ocs_status_code | pending_share_path | + | 1 | 100 | /sub | + | 2 | 200 | /sub | + + + Scenario Outline: multiple users share a file with the same name but different permissions to a user + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has uploaded file with content "First data" to "/randomfile.txt" + And user "Carol" has uploaded file with content "Second data" to "/randomfile.txt" + When user "Brian" shares file "randomfile.txt" with user "Alice" with permissions "read" using the sharing API + And user "Alice" accepts share "/randomfile.txt" offered by user "Brian" using the sharing API + Then as "Alice" the info about the last share by user "Brian" with user "Alice" should include + | uid_owner | %username% | + | share_with | %username% | + | file_target | | + | item_type | file | + | permissions | read | + When user "Carol" shares file "randomfile.txt" with user "Alice" with permissions "read,update" using the sharing API + And user "Alice" accepts share "/randomfile.txt" offered by user "Carol" using the sharing API + Then as "Alice" the info about the last share by user "Carol" with user "Alice" should include + | uid_owner | %username% | + | share_with | %username% | + | file_target | | + | item_type | file | + | permissions | read,update | + And the content of file "/Shares/randomfile.txt" for user "Alice" should be "First data" + And the content of file "/Shares/randomfile (2).txt" for user "Alice" should be "Second data" + @skipOnOcis + Examples: + | ocs_api_version | file_target_1 | file_target_2 | + | 1 | /Shares/randomfile.txt | /Shares/randomfile (2).txt | + | 2 | /Shares/randomfile.txt | /Shares/randomfile (2).txt | + + @skipOnOcV10 @issue-ocis-2131 + Examples: + | ocs_api_version | file_target_1 | file_target_2 | + | 1 | /randomfile.txt | /randomfile (2).txt | + | 2 | /randomfile.txt | /randomfile (2).txt | + + + Scenario Outline: multiple users share a folder with the same name to a user + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/zzzfolder" + And user "Brian" has created folder "zzzfolder/Brian" + And user "Carol" has created folder "/zzzfolder" + And user "Carol" has created folder "zzzfolder/Carol" + When user "Brian" shares folder "zzzfolder" with user "Alice" with permissions "read,delete" using the sharing API + And user "Alice" accepts share "/zzzfolder" offered by user "Brian" using the sharing API + Then as "Alice" the info about the last share by user "Brian" with user "Alice" should include + | uid_owner | %username% | + | share_with | %username% | + | file_target | | + | item_type | folder | + | permissions | read,delete | + When user "Carol" shares folder "zzzfolder" with user "Alice" with permissions "read,share" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Alice" should be able to accept pending share "/zzzfolder" offered by user "Carol" + Then as "Alice" the info about the last share by user "Carol" with user "Alice" should include + | uid_owner | %username% | + | share_with | %username% | + | file_target | | + | item_type | folder | + | permissions | read,share | + And as "Alice" folder "/Shares/zzzfolder/Brian" should exist + And as "Alice" folder "/Shares/zzzfolder (2)/Carol" should exist + @skipOnOcis + Examples: + | ocs_api_version | file_target_1 | file_target_2 | ocs_status_code | + | 1 | /Shares/zzzfolder | /Shares/zzzfolder (2) | 100 | + | 2 | /Shares/zzzfolder | /Shares/zzzfolder (2) | 200 | + + @skipOnOcV10 @issue-ocis-2131 + Examples: + | ocs_api_version | file_target_1 | file_target_2 | ocs_status_code | + | 1 | /zzzfolder | /zzzfolder (2) | 100 | + | 2 | /zzzfolder | /zzzfolder (2) | 200 | + + @skipOnEncryptionType:user-keys @encryption-issue-132 @skipOnLDAP @skipOnGraph + Scenario Outline: share with a group and then add a user to that group that already has a file with the shared name + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And these groups have been created: + | groupname | + | grp1 | + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "Shared content" to "lorem.txt" + And user "Carol" has uploaded file with content "My content" to "lorem.txt" + And user "Alice" has created a share with settings + | path | /lorem.txt | + | shareType | group | + | shareWith | grp1 | + And user "Brian" has accepted share "/lorem.txt" offered by user "Alice" + When the administrator adds user "Carol" to group "grp1" using the provisioning API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/lorem.txt" offered by user "Alice" + And the content of file "Shares/lorem.txt" for user "Brian" should be "Shared content" + And the content of file "lorem.txt" for user "Carol" should be "My content" + And the content of file "Shares/lorem.txt" for user "Carol" should be "Shared content" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Sharing parent folder to user with all permissions and its child folder to group with read permission then check create operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | user | + | shareWith | Brian | + | permissions | all | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Alice" accepts share "" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to create folder "/Shares/parent/fo1" + And user "Brian" should be able to create folder "/Shares/parent/child1/fo2" + And user "Alice" should not be able to create folder "/Shares/child1/fo3" + @issue-2440 + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to user with all permissions and its child folder to group with read permission then check rename operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has uploaded file with content "some data" to "/parent/child1/child2/textfile-2.txt" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | user | + | shareWith | Brian | + | permissions | all | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Alice" accepts share "" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to rename file "/Shares/parent/child1/child2/textfile-2.txt" to "/Shares/parent/child1/child2/rename.txt" + And user "Brian" should not be able to rename file "/Shares/child1/child2/rename.txt" to "/Shares/child1/child2/rename2.txt" + And user "Alice" should not be able to rename file "/Shares/child1/child2/rename.txt" to "/Shares/child1/child2/rename2.txt" + @issue-2440 + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to user with all permissions and its child folder to group with read permission then check delete operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has uploaded file with content "some data" to "/parent/child1/child2/textfile-2.txt" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | user | + | shareWith | Brian | + | permissions | all | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Alice" accepts share "" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to delete file "/Shares/parent/child1/child2/textfile-2.txt" + And user "Brian" should not be able to delete folder "/Shares/child1/child2" + And user "Alice" should not be able to delete folder "/Shares/child1/child2" + @issue-2440 + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to user with all permissions and its child folder to group with read permission then check reshare operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | user | + | shareWith | Brian | + | permissions | all | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + And user "Brian" has accepted share "/parent" offered by user "Carol" + And user "Brian" has accepted share "" offered by user "Carol" + And user "Alice" has accepted share "" offered by user "Carol" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/parent | + | shareType | user | + | shareWith | Alice | + | permissions | read | + Then the HTTP status code should be "200" + And the OCS status code should be "100" + And user "Alice" should be able to accept pending share "/parent" offered by user "Brian" + And as "Brian" folder "/Shares/child1" should exist + And as "Alice" folder "/Shares/child1" should exist + And as "Alice" folder "/Shares/parent" should exist + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to group with read permission and its child folder to user with all permissions then check create operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | user | + | shareWith | Brian | + | permissions | all | + When user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Alice" accepts share "/parent" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to create folder "/Shares/child1/fo1" + And user "Brian" should be able to create folder "/Shares/child1/child2/fo2" + But user "Brian" should not be able to create folder "/Shares/parent/fo3" + And user "Brian" should not be able to create folder "/Shares/parent/fo3" + And user "Alice" should not be able to create folder "/Shares/parent/fo3" + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to group with read permission and its child folder to user with all permissions then check rename operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has uploaded file with content "some data" to "/parent/child1/child2/textfile-2.txt" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | user | + | shareWith | Brian | + | permissions | all | + When user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Alice" accepts share "/parent" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to rename file "/Shares/child1/child2/textfile-2.txt" to "/Shares/child1/child2/rename.txt" + And user "Brian" should not be able to rename file "/Shares/parent/child1/child2/rename.txt" to "/Shares/parent/child1/child2/rename2.txt" + And user "Alice" should not be able to rename file "/Shares/parent/child1/child2/rename.txt" to "/Shares/parent/child1/child2/rename2.txt" + @issue-ocis-2440 + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to group with read permission and its child folder to user with all permissions then check delete operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has uploaded file with content "some data" to "/parent/child1/child2/textfile-2.txt" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | user | + | shareWith | Brian | + | permissions | all | + When user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Alice" accepts share "/parent" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to delete file "/Shares/child1/child2/textfile-2.txt" + And user "Brian" should not be able to delete folder "/Shares/parent/child1" + And user "Alice" should not be able to delete folder "/Shares/parent/child1" + @issue-ocis-2440 + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to group with read permission and its child folder to user with all permissions then check reshare operation + Given group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | group | + | shareWith | grp1 | + | permissions | read | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | user | + | shareWith | Brian | + | permissions | all | + When user "Brian" accepts share "" offered by user "Carol" using the sharing API + And user "Brian" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Alice" accepts share "/parent" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Brian" should be able to share folder "/Shares/child1" with user "Alice" with permissions "read" using the sharing API + And user "Alice" should be able to accept pending share "" offered by user "Brian" + And as "Brian" folder "/Shares/parent" should exist + And as "Alice" folder "/Shares/parent" should exist + And as "Alice" folder "/Shares/child1" should exist + Examples: + | path | + | /child1 | + + + Scenario Outline: Sharing parent folder to one group with all permissions and its child folder to another group with read permission + Given these groups have been created: + | groupname | + | grp1 | + | grp2 | + | grp3 | + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has created the following folders + | path | + | /parent | + | /parent/child1 | + | /parent/child1/child2 | + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Carol" has uploaded file with content "some data" to "/parent/child1/child2/textfile-2.txt" + And user "Carol" has created a share with settings + | path | /parent | + | shareType | group | + | shareWith | grp1 | + | permissions | all | + And user "Carol" has created a share with settings + | path | /parent/child1 | + | shareType | group | + | shareWith | grp2 | + | permissions | read | + When user "Alice" accepts share "/parent" offered by user "Carol" using the sharing API + And user "Brian" accepts share "" offered by user "Carol" using the sharing API + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on all endpoints should be "100" + And user "Alice" should be able to create folder "/Shares/parent/child1/fo1" + And user "Alice" should be able to create folder "/Shares/parent/child1/child2/fo2" + And user "Alice" should be able to delete folder "/Shares/parent/child1/fo1" + And user "Alice" should be able to delete folder "/Shares/parent/child1/child2/fo2" + And user "Alice" should be able to rename file "/Shares/parent/child1/child2/textfile-2.txt" to "/Shares/parent/child1/child2/rename.txt" + And user "Alice" should be able to share folder "/Shares/parent/child1" with group "grp3" with permissions "all" using the sharing API + And as "Brian" folder "/Shares/child1" should exist + And user "Brian" should not be able to create folder "/Shares/child1/fo1" + And user "Brian" should not be able to create folder "/Shares/child1/child2/fo2" + And user "Brian" should not be able to rename file "/Shares/child1/child2/rename.txt" to "/Shares/child1/child2/rename2.txt" + And user "Brian" should not be able to share folder "/Shares/child1" with group "grp3" with permissions "read" using the sharing API + Examples: + | path | + | /child1 | + + @skipOnOcV10 @issue-39347 + Scenario Outline: Share receiver renames the received group share and shares same folder through user share again + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "read" using the sharing API + # Note: Brian has already accepted the share of this resource as a member of "grp". + # Now he has also received the same resource shared directly to "Brian". + # The server should effectively "auto-accept" this new "copy" of the resource + # and present to Brian only the single resource "Shares/sharedParent" + And as "Brian" folder "Shares/parent" should not exist + And as "Brian" folder "Shares/sharedParent" should exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @skipOnOcV10 @issue-39347 + Scenario Outline: Share receiver renames a group share and receives same resource through user share with additional permissions + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | read | + And user "Brian" has accepted share "/parent" offered by user "Alice" + And user "Brian" has moved folder "/Shares/parent" to "/Shares/sharedParent" + When user "Alice" shares folder "parent" with user "Brian" with permissions "all" using the sharing API + # Note: Brian has already accepted the share of this resource as a member of "grp". + # Now he has also received the same resource shared directly to "Brian". + # The server should effectively "auto-accept" this new "copy" of the resource + # and present to Brian only the single resource "Shares/sharedParent" + Then as "Brian" folder "Shares/parent" should not exist + And as "Brian" folder "Shares/sharedParent" should exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should exist + Examples: + | ocs_api_version | + | 1 | + | 2 | + + @skipOnOcV10 @issue-39347 + Scenario Outline: Share receiver renames a group share and receives same resource through user share with less permissions + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has shared folder "parent" with group "grp" with permissions "all" + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "read" using the sharing API + # Note: Brian has already accepted the share of this resource as a member of "grp". + # Now he has also received the same resource shared directly to "Brian". + # The server should effectively "auto-accept" this new "copy" of the resource + # and present to Brian only the single resource "Shares/sharedParent" + And as "Brian" folder "Shares/parent" should not exist + And as "Brian" folder "Shares/sharedParent" should exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedMultipleWaysIssue39347.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedMultipleWaysIssue39347.feature new file mode 100644 index 00000000000..de6cc55f527 --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedMultipleWaysIssue39347.feature @@ -0,0 +1,130 @@ +@api @files_sharing-app-required @notToImplementOnOCIS +Feature: share resources where the sharee receives the share in multiple ways + + # These are the bug demonstration scenarios for https://github.com/owncloud/core/issues/39347 + # Once the issue is fixed, delete this file and unskip all the respective tests tagged with @issue-39347 + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: Share receiver renames the received group share and shares same folder through user share again + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "read" using the sharing API + And user "Brian" should be able to accept pending share "/parent" offered by user "Alice" + And as "Brian" folder "Shares/parent" should exist + And as "Brian" folder "Shares/sharedParent" should not exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + # Note: after fixing the bug, this scenario is no longer relevant. + # Brian should not get a chance to decline (or accept) the 2nd share of the resource from Alice + Scenario Outline: Share receiver renames the received group share and declines another share of same folder through user share again + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "read" using the sharing API + And user "Brian" should be able to decline pending share "/parent" offered by user "Alice" + And as "Brian" folder "Shares/parent" should not exist + And as "Brian" folder "Shares/sharedParent" should not exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Share receiver renames a group share and receives same resource through user share with additional permissions + Given using OCS API version "" + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | read | + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "all" using the sharing API + And user "Brian" should be able to accept pending share "/parent" offered by user "Alice" + And as "Brian" folder "Shares/parent" should exist + And as "Brian" folder "Shares/sharedParent" should not exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should not exist + And as "Brian" file "Shares/parent/child/lorem.txt" should exist + And user "Brian" should be able to delete file "Shares/parent/child/lorem.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Share receiver renames a group share and receives same resource through user share with less permissions + Given using OCS API version " " + And group "grp" has been created + And user "Brian" has been added to group "grp" + And user "Alice" has been added to group "grp" + And user "Alice" has created folder "parent" + And user "Alice" has created folder "parent/child" + And user "Alice" has uploaded file with content "Share content" to "parent/child/lorem.txt" + And user "Alice" has created a share with settings + | path | parent | + | shareType | group | + | shareWith | grp | + | permissions | all | + When user "Brian" accepts share "/parent" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Brian" should be able to rename folder "/Shares/parent" to "/Shares/sharedParent" + And user "Alice" should be able to share folder "parent" with user "Brian" with permissions "read" using the sharing API + And user "Brian" should be able to accept pending share "/parent" offered by user "Alice" + And as "Brian" folder "Shares/parent" should exist + And as "Brian" folder "Shares/sharedParent" should not exist + And as "Brian" file "Shares/sharedParent/child/lorem.txt" should not exist + And as "Brian" file "Shares/parent/child/lorem.txt" should exist + And user "Brian" should be able to delete file "Shares/parent/child/lorem.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature new file mode 100644 index 00000000000..0ddcd246af9 --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature @@ -0,0 +1,127 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: cannot share resources outside the group when share with membership groups is enabled + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: sharer should not be able to share a folder to a group which he/she is not member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And group "grp1" has been created + And user "Alice" has been added to group "grp0" + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Brian" folder "/PARENT" should not exist + And as "Brian" folder "/Shares/PARENT" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | + + + Scenario Outline: sharer should be able to share a folder to a user who is not member of sharer group when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And user "Alice" has created folder "PARENT" + When user "Alice" shares folder "/PARENT" with user "Brian" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/PARENT" should exist + But as "Brian" folder "/PARENT" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: sharer should be able to share a folder to a group which he/she is member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And user "Brian" has been added to group "grp0" + And user "Alice" has created folder "PARENT" + When user "Alice" shares folder "/PARENT" with group "grp0" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/PARENT" should exist + But as "Brian" folder "/PARENT" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: sharer should not be able to share a file to a group which he/she is not member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And group "grp1" has been created + And user "Alice" has been added to group "grp0" + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Brian" file "/textfile0.txt" should not exist + And as "Brian" file "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | + + + Scenario Outline: sharer should be able to share a file to a group which he/she is member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And user "Brian" has been added to group "grp0" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" shares folder "/textfile0.txt" with group "grp0" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" file "/Shares/textfile0.txt" should exist + But as "Brian" file "/textfile0.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: sharer should be able to share a file to a user who is not a member of sharer group when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp0" has been created + And user "Alice" has been added to group "grp0" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" shares folder "/textfile0.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" file "/Shares/textfile0.txt" should exist + But as "Brian" file "/textfile0.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature new file mode 100644 index 00000000000..365d81730f3 --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithDisabledUser.feature @@ -0,0 +1,30 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: share resources with a disabled user + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + + @issue-ocis-2212 + Scenario Outline: Creating a new share with a disabled user + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has been disabled + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "401" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 997 | + + @issue-32068 + Scenario: Creating a new share with a disabled user + Given using OCS API version "2" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has been disabled + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "997" + #And the OCS status code should be "401" + And the HTTP status code should be "401" diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature new file mode 100644 index 00000000000..3f654ac014f --- /dev/null +++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature @@ -0,0 +1,118 @@ +@api @files_sharing-app-required +Feature: cannot share resources with invalid permissions + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + + + Scenario Outline: Cannot create a share of a file or folder with invalid permissions + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + When user "Alice" creates a share using the sharing API with settings + | path | | + | shareWith | Brian | + | shareType | user | + | permissions | | + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Brian" entry "" should not exist + And as "Brian" entry "/Shares/" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | ocs_status_code | http_status_code | item | permissions | + | 1 | 400 | 200 | textfile0.txt | 0 | + | 2 | 400 | 400 | textfile0.txt | 0 | + | 1 | 400 | 200 | PARENT | 0 | + | 2 | 400 | 400 | PARENT | 0 | + | 1 | 404 | 200 | textfile0.txt | 32 | + | 2 | 404 | 404 | textfile0.txt | 32 | + | 1 | 404 | 200 | PARENT | 32 | + | 2 | 404 | 404 | PARENT | 32 | + + + Scenario Outline: Cannot create a share of a file with a user with only create permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareWith | Brian | + | shareType | user | + | permissions | create | + Then the OCS status code should be "400" + And the HTTP status code should be "" + And as "Brian" entry "textfile0.txt" should not exist + And as "Brian" entry "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + + Scenario Outline: Cannot create a share of a file with a user with only (create,delete) permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareWith | Brian | + | shareType | user | + | permissions | | + Then the OCS status code should be "400" + And the HTTP status code should be "" + And as "Brian" entry "textfile0.txt" should not exist + And as "Brian" entry "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | permissions | + | 1 | 200 | delete | + | 2 | 400 | delete | + | 1 | 200 | create,delete | + | 2 | 400 | create,delete | + + @issue-ocis-reva-34 + Scenario Outline: Cannot create a share of a file with a group with only create permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareWith | grp1 | + | shareType | group | + | permissions | create | + Then the OCS status code should be "400" + And the HTTP status code should be "" + And as "Brian" entry "textfile0.txt" should not exist + And as "Brian" entry "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + @issue-ocis-reva-34 + Scenario Outline: Cannot create a share of a file with a group with only (create,delete) permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + When user "Alice" creates a share using the sharing API with settings + | path | textfile0.txt | + | shareWith | grp1 | + | shareType | group | + | permissions | | + Then the OCS status code should be "400" + And the HTTP status code should be "" + And as "Brian" entry "textfile0.txt" should not exist + And as "Brian" entry "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | permissions | + | 1 | 200 | delete | + | 2 | 400 | delete | + | 1 | 200 | create,delete | + | 2 | 400 | create,delete | diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareFromLocalStorageToSharesFolder.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareFromLocalStorageToSharesFolder.feature new file mode 100644 index 00000000000..d649a328ce3 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareFromLocalStorageToSharesFolder.feature @@ -0,0 +1,114 @@ +@api @local_storage @files_external-app-required @notToImplementOnOCIS @files_sharing-app-required +Feature: local-storage + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @skipOnEncryptionType:user-keys @encryption-issue-181 + Scenario Outline: Share a file inside a local external storage + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/local_storage/filetoshare.txt" + When user "Alice" shares file "/local_storage/filetoshare.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/filetoshare.txt | + | path | /local_storage/filetoshare.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + When user "Brian" accepts share "" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as "Brian" file "/Shares/filetoshare.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | pending_share_path | + | 1 | 100 | /filetoshare.txt | + | 2 | 200 | /filetoshare.txt | + + + Scenario Outline: Share a folder inside a local external storage + Given using OCS API version "" + And user "Alice" has created folder "/local_storage/foo" + When user "Alice" shares folder "/local_storage/foo" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/foo | + | path | /local_storage/foo | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | user | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @skipOnEncryptionType:user-keys @encryption-issue-181 + Scenario Outline: Share a file inside a local external storage to a group + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/local_storage/filetoshare.txt" + When user "Alice" shares file "/local_storage/filetoshare.txt" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | share_with | grp1 | + | share_with_displayname | grp1 | + | file_target | /Shares/filetoshare.txt | + | path | /local_storage/filetoshare.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | group | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Share a folder inside a local external storage to a group + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Alice" has created folder "/local_storage/foo" + When user "Alice" shares folder "/local_storage/foo" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | share_with | grp1 | + | share_with_displayname | grp1 | + | file_target | /Shares/foo | + | path | /local_storage/foo | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | group | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature new file mode 100644 index 00000000000..a7931f60b74 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature @@ -0,0 +1,783 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest @skipOnEncryptionType:user-keys @issue-32322 + Scenario Outline: Creating a share of a file with a user, the default permissions are read(1)+update(2)+can-share(16) + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | share_with_user_type | 0 | + | file_target | /Shares/textfile0.txt | + | path | /textfile0.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @skipOnEncryptionType:user-keys @issue-32322 @issue-ocis-2133 + Scenario Outline: Creating a share of a file containing commas in the filename, with a user, the default permissions are read(1)+update(2)+can-share(16) + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file with comma in filename" to "/sample,1.txt" + When user "Alice" shares file "sample,1.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/sample,1.txt | + | path | /sample,1.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + When user "Brian" accepts share "/sample,1.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/sample,1.txt" for user "Brian" should be "file with comma in filename" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-2133 @issue-ocis-reva-301 @issue-ocis-reva-302 + Scenario Outline: Creating a share of a file with a user and asking for various permission combinations + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" shares file "textfile0.txt" with user "Brian" with permissions using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/textfile0.txt | + | path | /textfile0.txt | + | permissions | | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + Examples: + | ocs_api_version | requested_permissions | granted_permissions | ocs_status_code | + # Ask for full permissions. You get share plus read plus update. create and delete do not apply to shares of a file + | 1 | 31 | 19 | 100 | + | 2 | 31 | 19 | 200 | + # Ask for read, share (17), create and delete. You get share plus read + | 1 | 29 | 17 | 100 | + | 2 | 29 | 17 | 200 | + # Ask for read, update, create, delete. You get read plus update. + | 1 | 15 | 3 | 100 | + | 2 | 15 | 3 | 200 | + # Ask for just update. You get exactly update (you do not get read or anything else) + | 1 | 2 | 2 | 100 | + | 2 | 2 | 2 | 200 | + + + Scenario Outline: Creating a share of a file with no permissions should fail + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "Random data" to "randomfile.txt" + When user "Alice" shares file "randomfile.txt" with user "Brian" with permissions "0" using the sharing API + Then the OCS status code should be "400" + And the HTTP status code should be "" + And the sharing API should report that no shares are shared with user "Brian" + And as "Brian" file "/Shares/randomfile.txt" should not exist + And as "Brian" file "randomfile.txt" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + + Scenario Outline: Creating a share of a folder with no permissions should fail + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/afolder" + When user "Alice" shares folder "afolder" with user "Brian" with permissions "0" using the sharing API + Then the OCS status code should be "400" + And the HTTP status code should be "" + And the sharing API should report that no shares are shared with user "Brian" + And as "Brian" folder "/Shares/afolder" should not exist + And as "Brian" folder "afolder" should not exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + @issue-ocis-2133 + Scenario Outline: Creating a share of a folder with a user, the default permissions are all permissions(31) + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/FOLDER" + When user "Alice" shares folder "/FOLDER" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/FOLDER | + | path | /FOLDER | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | user | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-34 + Scenario Outline: Creating a share of a file with a group, the default permissions are read(1)+update(2)+can-share(16) + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | share_with | grp1 | + | share_with_displayname | grp1 | + | file_target | /Shares/textfile0.txt | + | path | /textfile0.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | group | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-34 + Scenario Outline: Creating a share of a folder with a group, the default permissions are all permissions(31) + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has created folder "/FOLDER" + When user "Alice" shares folder "/FOLDER" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | grp1 | + | share_with_displayname | grp1 | + | file_target | /Shares/FOLDER | + | path | /FOLDER | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | group | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: Share of folder to a group + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "file in parent folder" to "/PARENT/parent.txt" + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + And user "Carol" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @skipOnReva # reva doesn't have a pre-created admin user + Scenario Outline: User included in multiple groups receives a share from the admin + Given using OCS API version "" + And group "grp1" has been created + And group "grp2" has been created + And user "Alice" has been added to group "grp1" + And user "Alice" has been added to group "grp2" + And admin has created folder "/PARENT" + When user "admin" shares folder "/PARENT" with group "grp1" using the sharing API + And user "Alice" accepts share "/PARENT" offered by user "admin" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: user included in multiple groups, shares a folder with a group + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + And group "grp1" has been created + And group "grp2" has been created + And user "Alice" has been added to group "grp1" + And user "Alice" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "/PARENT" + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: sharing again an own file while belonging to a group + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has uploaded file with content "ownCloud test text file 0" to "/randomfile.txt" + And user "Brian" has shared file "randomfile.txt" with group "grp1" + And user "Brian" has deleted the last share + When user "Brian" shares file "/randomfile.txt" with group "grp1" using the sharing API + And user "Alice" accepts share "/randomfile.txt" offered by user "Brian" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Alice" file "/Shares/randomfile.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-2201 + Scenario Outline: sharing subfolder of already shared folder, GET result is correct + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + | David | + | Emily | + And user "Alice" has created folder "/folder1" + And user "Alice" has shared folder "/folder1" with user "Brian" + And user "Alice" has shared folder "/folder1" with user "Carol" + And user "Alice" has created folder "/folder1/folder2" + And user "Alice" has shared folder "/folder1/folder2" with user "David" + And user "Alice" has shared folder "/folder1/folder2" with user "Emily" + When user "Alice" sends HTTP method "GET" to OCS API endpoint "/apps/files_sharing/api/v1/shares" + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the response should contain 4 entries + And folder "/folder1" should be included as path in the response + And folder "/folder1/folder2" should be included as path in the response + When user "Alice" sends HTTP method "GET" to OCS API endpoint "/apps/files_sharing/api/v1/shares?path=/folder1/folder2" + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the response should contain 2 entries + And folder "/folder1" should not be included as path in the response + And folder "/folder1/folder2" should be included as path in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: user shares a file with file name longer than 64 chars to another user + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has moved file "textfile0.txt" to "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" + When user "Alice" shares file "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" file "/Shares/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: user shares a file with file name longer than 64 chars to a group + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has moved file "textfile0.txt" to "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" + When user "Alice" shares file "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" with group "grp1" using the sharing API + And user "Brian" accepts share "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" file "/Shares/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: user shares a folder with folder name longer than 64 chars to another user + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test" to "/textfile0.txt" + And user "Alice" has created folder "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" + And user "Alice" has moved file "textfile0.txt" to "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog/textfile0.txt" + When user "Alice" shares folder "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" with user "Brian" using the sharing API + And user "Brian" accepts share "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And the content of file "/Shares/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog/textfile0.txt" for user "Brian" should be "ownCloud test" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: user shares a folder with folder name longer than 64 chars to a group + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "ownCloud test" to "/textfile0.txt" + And user "Alice" has created folder "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" + And user "Alice" has moved file "textfile0.txt" to "aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog/textfile0.txt" + When user "Alice" shares folder "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" with group "grp1" using the sharing API + And user "Brian" accepts share "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And the content of file "/Shares/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog/textfile0.txt" for user "Brian" should be "ownCloud test" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-11 + Scenario: share with user when username contains capital letters + Given these users have been created without skeleton files: + | username | + | brian | + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" shares file "/randomfile.txt" with user "BRIAN" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "BRIAN" should include + | share_with | brian | + | file_target | /Shares/randomfile.txt | + | path | /randomfile.txt | + | permissions | share,read,update | + | uid_owner | %username% | + When user "brian" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And user "brian" should see the following elements + | /Shares/randomfile.txt | + And the content of file "Shares/randomfile.txt" for user "brian" should be "Random data" + + @skipOnLDAP + Scenario: creating a new share with user of a group when username contains capital letters + Given these users have been created without skeleton files: + | username | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has shared file "randomfile.txt" with group "grp1" + When user "Brian" accepts share "/randomfile.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And user "Brian" should see the following elements + | /Shares/randomfile.txt | + And the content of file "/Shares/randomfile.txt" for user "Brian" should be "Random data" + + @issue-ocis-reva-34 + Scenario Outline: Share of folder to a group with emoji in the name + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And group "😀 😁" has been created + And user "Brian" has been added to group "😀 😁" + And user "Carol" has been added to group "😀 😁" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "file in parent folder" to "/PARENT/parent.txt" + When user "Alice" shares folder "/PARENT" with group "😀 😁" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" accepts share "/PARENT" offered by user "Alice" using the sharing API + And the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + And user "Carol" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @skipOnEncryptionType:user-keys @encryption-issue-132 @skipOnLDAP @skipOnGraph + Scenario Outline: share with a group and then add a user to that group + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And these groups have been created: + | groupname | + | grp1 | + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "some content" to "lorem.txt" + When user "Alice" shares file "lorem.txt" with group "grp1" using the sharing API + And user "Brian" accepts share "/lorem.txt" offered by user "Alice" using the sharing API + And the administrator adds user "Carol" to group "grp1" using the provisioning API + And user "Carol" accepts share "/lorem.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "" + And the HTTP status code of responses on all endpoints should be "200" + And the content of file "/Shares/lorem.txt" for user "Brian" should be "some content" + And the content of file "/Shares/lorem.txt" for user "Carol" should be "some content" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @skipOnLDAP + # deleting an LDAP group is not relevant or possible using the provisioning API + Scenario Outline: shares shared to deleted group should not be available + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with group "grp1" + When user "Alice" sends HTTP method "GET" to OCS API endpoint "/apps/files_sharing/api/v1/shares" + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | share_with | grp1 | + | file_target | | + | path | /textfile0.txt | + | uid_owner | %username% | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then as "Brian" file "/Shares/textfile0.txt" should exist + And as "Carol" file "/Shares/textfile0.txt" should exist + When the administrator deletes group "grp1" using the provisioning API + And user "Alice" sends HTTP method "GET" to OCS API endpoint "/apps/files_sharing/api/v1/shares" + Then the OCS status code should be "" + And the HTTP status code should be "200" + And file "/textfile0.txt" should not be included as path in the response + And as "Brian" file "/Shares/textfile0.txt" should not exist + And as "Carol" file "/Shares/textfile0.txt" should not exist + @skipOnOcis + Examples: + | ocs_api_version | ocs_status_code | path | + | 1 | 100 | /Shares/textfile0.txt | + | 2 | 200 | /Shares/textfile0.txt | + @skipOnOcV10 @issue-ocis-2441 + Examples: + | ocs_api_version | ocs_status_code | path | + | 1 | 100 | /textfile0.txt | + | 2 | 200 | /textfile0.txt | + + @skipOnFilesClassifier @issue-files-classifier-291 @issue-ocis-2146 + Scenario: Share a file by multiple channels and download from sub-folder and direct file share + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/common" + And user "Alice" has created folder "/common/sub" + And user "Alice" has shared folder "common" with group "grp1" + And user "Brian" has accepted share "/common" offered by user "Alice" + And user "Carol" has accepted share "/common" offered by user "Alice" + And user "Brian" has uploaded file with content "ownCloud" to "/textfile0.txt" + And user "Brian" has shared file "textfile0.txt" with user "Carol" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Brian" + And user "Brian" has moved file "/textfile0.txt" to "/Shares/common/textfile0.txt" + And user "Brian" has moved file "/Shares/common/textfile0.txt" to "/Shares/common/sub/textfile0.txt" + When user "Carol" uploads file "filesForUpload/file_to_overwrite.txt" to "/Shares/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/Shares/common/sub/textfile0.txt" for user "Carol" should be "BLABLABLA" plus end-of-line + And the content of file "/Shares/textfile0.txt" for user "Carol" should be "BLABLABLA" plus end-of-line + And user "Carol" should see the following elements + | /Shares/common/sub/textfile0.txt | + | /Shares/textfile0.txt | + And the content of file "/Shares/common/sub/textfile0.txt" for user "Brian" should be "BLABLABLA" plus end-of-line + And the content of file "/common/sub/textfile0.txt" for user "Alice" should be "BLABLABLA" plus end-of-line + + @issue-enterprise-3896 @issue-ocis-2201 + Scenario Outline: sharing back to resharer is allowed + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has created folder "userZeroFolder" + And user "Alice" has shared folder "userZeroFolder" with user "Brian" + And user "Brian" has accepted share "/userZeroFolder" offered by user "Alice" + And user "Brian" has created folder "/Shares/userZeroFolder/userOneFolder" + And user "Brian" has shared folder "/Shares/userZeroFolder/userOneFolder" with user "Carol" with permissions "read, share" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Carol" shares folder "/Shares/userOneFolder" with user "Brian" using the sharing API + Then the HTTP status code should be "200" +# Then the HTTP status code should be "405" + And the sharing API should report to user "Brian" that no shares are in the pending state + And as "Brian" folder "/Shares/userOneFolder" should not exist + Examples: + | pending_share_path | + | /userOneFolder | + + @issue-enterprise-3896 @issue-ocis-2201 + Scenario Outline: sharing back to original sharer is allowed + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has created folder "userZeroFolder" + And user "Alice" has shared folder "userZeroFolder" with user "Brian" + And user "Brian" has accepted share "/userZeroFolder" offered by user "Alice" + And user "Brian" has created folder "/Shares/userZeroFolder/userOneFolder" + And user "Brian" has shared folder "/Shares/userZeroFolder/userOneFolder" with user "Carol" with permissions "read, share" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Carol" shares folder "/Shares/userOneFolder" with user "Alice" using the sharing API + Then the HTTP status code should be "200" +# Then the HTTP status code should be "405" + And the sharing API should report to user "Alice" that no shares are in the pending state + And as "Alice" folder "/Shares/userOneFolder" should not exist + Examples: + | pending_share_path | + | /userOneFolder | + + @issue-enterprise-3896 @issue-ocis-2201 + Scenario Outline: sharing a subfolder to a user that already received parent folder share + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + | David | + And user "Alice" has created folder "userZeroFolder" + And user "Alice" has shared folder "userZeroFolder" with user "Brian" + And user "Alice" has shared folder "userZeroFolder" with user "Carol" + And user "Brian" has accepted share "/userZeroFolder" offered by user "Alice" + And user "Carol" has accepted share "/userZeroFolder" offered by user "Alice" + And user "Brian" has created folder "/Shares/userZeroFolder/userOneFolder" + And user "Brian" has shared folder "/Shares/userZeroFolder/userOneFolder" with user "David" with permissions "read, share" + And user "David" has accepted share "" offered by user "Brian" + When user "David" shares folder "/Shares/userOneFolder" with user "Carol" using the sharing API + Then the HTTP status code should be "200" +# Then the HTTP status code should be "405" + And the sharing API should report to user "Carol" that no shares are in the pending state + And as "Carol" folder "/Shares/userOneFolder" should not exist + Examples: + | pending_share_path | + | /userOneFolder | + + @smokeTest + Scenario Outline: Creating a share of a renamed file + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has moved file "/textfile0.txt" to "/renamed.txt" + When user "Alice" shares file "renamed.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/renamed.txt | + | path | /renamed.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + When user "Brian" accepts share "/renamed.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the content of file "/Shares/renamed.txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-903 + Scenario Outline: shares to a deleted user should not be listed as shares for the sharer + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile0.txt" with user "Carol" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Alice" + And the administrator has deleted user "Brian" using the provisioning API + When user "Alice" gets all the shares from the file "textfile0.txt" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be included in the response + But user "Brian" should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-719 + Scenario Outline: Creating a share of a renamed file when another share exists + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/Folder1" + And user "Alice" has created folder "/Folder2" + And user "Alice" has shared folder "/Folder1" with user "Brian" + And user "Brian" has accepted share "/Folder1" offered by user "Alice" + And user "Alice" has moved file "/Folder2" to "/renamedFolder2" + When user "Alice" shares folder "/renamedFolder2" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/renamedFolder2 | + | path | /renamedFolder2 | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | user | + When user "Brian" accepts share "/renamedFolder2" offered by user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as "Brian" folder "/Shares/renamedFolder2" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1710 + Scenario Outline: Sharing a same file twice to the same group is not possible + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + When user "Alice" shares file "textfile0.txt" with group "grp1" using the sharing API + Then the HTTP status code should be "" + And the OCS status code should be "403" + And the OCS status message should be "Path already shared with this group" + Examples: + | ocs-api-version | http-status | + | 1 | 200 | + | 2 | 403 | + + @issue-ocis-2215 + Scenario Outline: Sharing the shares folder to users is not possible + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares folder "Shares" with user "Carol" using the sharing API + Then the HTTP status code should be "" + And the OCS status code should be "403" + And the OCS status message should be "Path contains files shared with you" + Examples: + | ocs-api-version | http-status | + | 1 | 200 | + | 2 | 403 | + + @issue-ocis-2215 + Scenario Outline: Sharing the shares folder to groups is not possible + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "share_group" has been created + And user "Carol" has been added to group "share_group" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares folder "Shares" with group "share_group" using the sharing API + Then the HTTP status code should be "" + And the OCS status code should be "403" + And the OCS status message should be "Path contains files shared with you" + Examples: + | ocs-api-version | http-status | + | 1 | 200 | + | 2 | 403 | + + @issue-ocis-2215 + Scenario Outline: Sharing the shares folder as public link is not possible + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a public link share of folder "Shares" using the sharing API + Then the HTTP status code should be "" + And the OCS status code should be "403" + And the OCS status message should be "Path contains files shared with you" + Examples: + | ocs-api-version | http-status | + | 1 | 200 | + | 2 | 403 | diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature new file mode 100644 index 00000000000..7497376181d --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature @@ -0,0 +1,227 @@ +@api @files_sharing-app-required @issue-ocis-1328 @issue-ocis-1289 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + + + Scenario Outline: Delete all group shares + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has moved file "/Shares/textfile0.txt" to "/Shares/anotherName.txt" + When user "Alice" deletes the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should not see the share id of the last share + And as "Brian" file "/Shares/textfile0.txt" should not exist + And as "Brian" file "/Shares/anotherName.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: delete a share + Given using OCS API version "" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" deletes the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share id should not be included in the response + And as "Brian" file "/Shares/textfile0.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: orphaned shares + Given using OCS API version "1" + And user "Alice" has created folder "/common" + And user "Alice" has created folder "/common/sub" + And user "Alice" has shared folder "/common/sub" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Alice" deletes folder "/common" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" folder "/Shares/sub" should not exist + And as "Brian" folder "/sub" should not exist + Examples: + | pending_share_path | + | /sub | + + @smokeTest @files_trashbin-app-required + Scenario: deleting a file out of a share as recipient creates a backup for the owner + Given using OCS API version "1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Brian" deletes file "/Shares/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" file "/Shares/shared/shared_file.txt" should not exist + And as "Alice" file "/shared/shared_file.txt" should not exist + And as "Alice" file "/shared_file.txt" should exist in the trashbin + And as "Brian" file "/shared_file.txt" should exist in the trashbin + + @files_trashbin-app-required + Scenario: deleting a folder out of a share as recipient creates a backup for the owner + Given using OCS API version "1" + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Brian" deletes folder "/Shares/shared/sub" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" folder "/Shares/shared/sub" should not exist + And as "Alice" folder "/shared/sub" should not exist + And as "Alice" folder "/sub" should exist in the trashbin + And as "Alice" file "/sub/shared_file.txt" should exist in the trashbin + And as "Brian" folder "/sub" should exist in the trashbin + And as "Brian" file "/sub/shared_file.txt" should exist in the trashbin + + @smokeTest + Scenario Outline: unshare from self + And group "grp1" has been created + And these users have been created with default attributes and without skeleton files: + | username | + | Carol | + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Carol" has created folder "PARENT" + And user "Carol" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Carol" has shared file "/PARENT/parent.txt" with group "grp1" + And user "Brian" has accepted share "" offered by user "Carol" + And user "Carol" has stored etag of element "/PARENT" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + When user "Brian" unshares file "/Shares/parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the etag of element "/" of user "Brian" should have changed + And the etag of element "/Shares" of user "Brian" should have changed + And the etag of element "/PARENT" of user "Carol" should not have changed + Examples: + | pending_share_path | + | /parent.txt | + + + Scenario: sharee of a read-only share folder tries to delete the shared folder + Given using OCS API version "1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "shared" with user "Brian" with permissions "read" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Brian" deletes file "/Shares/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/shared/shared_file.txt" should exist + And as "Brian" file "/Shares/shared/shared_file.txt" should exist + + + Scenario: sharee of a upload-only shared folder tries to delete a file in the shared folder + Given using OCS API version "1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "shared" with user "Brian" with permissions "create" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Brian" deletes file "/Shares/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/shared/shared_file.txt" should exist + # Note: for Brian, the file does not "exist" because he only has "create" permission, not "read" + And as "Brian" file "/Shares/shared/shared_file.txt" should not exist + + + Scenario: sharee of an upload-only shared folder tries to delete their file in the folder + Given using OCS API version "1" + And user "Alice" has created folder "/shared" + And user "Alice" has shared folder "shared" with user "Brian" with permissions "create" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/Shares/shared/textfile.txt" + When user "Brian" deletes file "/Shares/shared/textfile.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/shared/textfile.txt" should exist + # Note: for Brian, the file does not "exist" because he only has "create" permission, not "read" + And as "Brian" file "/Shares/shared/textfile.txt" should not exist + + + Scenario Outline: A Group share recipient tries to delete the share + Given using OCS API version "" + And group "grp1" has been created + And these users have been created with default attributes and without skeleton files: + | username | + | Carol | + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared entry "" with group "grp1" + And user "Brian" has accepted share "" offered by user "Alice" + And user "Carol" has accepted share "" offered by user "Alice" + When user "Brian" deletes the last share of user "Alice" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Alice" entry "" should exist + And as "Brian" entry "" should exist + And as "Carol" entry "" should exist + Examples: + | entry_to_share | ocs_api_version | http_status_code | received_entry | pending_entry | + | /shared/shared_file.txt | 1 | 200 | /Shares/shared_file.txt | /Shares/shared_file.txt | + | /shared/shared_file.txt | 2 | 404 | /Shares/shared_file.txt | /Shares/shared_file.txt | + | /shared | 1 | 200 | /Shares/shared | /Shares/shared | + | /shared | 2 | 404 | /Shares/shared | /Shares/shared | + + + Scenario Outline: An individual share recipient tries to delete the share + Given using OCS API version "" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared entry "" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" deletes the last share of user "Alice" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Alice" entry "" should exist + And as "Brian" entry "" should exist + Examples: + | entry_to_share | ocs_api_version | http_status_code | received_entry | pending_entry | + | /shared/shared_file.txt | 1 | 200 | /Shares/shared_file.txt | /Shares/shared_file.txt | + | /shared/shared_file.txt | 2 | 404 | /Shares/shared_file.txt | /Shares/shared_file.txt | + | /shared | 1 | 200 | /Shares/shared | /Shares/shared | + | /shared | 2 | 404 | /Shares/shared | /Shares/shared | + + @issue-ocis-720 + Scenario Outline: request PROPFIND after sharer deletes the collaborator + Given using OCS API version "" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" deletes the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Brian" requests "/remote.php/dav/files/%username%" with "PROPFIND" using basic auth + Then the HTTP status code should be "207" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1229 + Scenario Outline: delete a share with wrong authentication + Given using OCS API version "" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" tries to delete the last share of user "Alice" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 404 | 200 | + | 2 | 404 | 404 | diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature new file mode 100644 index 00000000000..4cad03c72f5 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature @@ -0,0 +1,174 @@ +@api @files_sharing-app-required +Feature: Exclude groups from receiving shares + As an admin + I want to exclude groups from receiving shares + So that users do not mistakenly share with groups they should not e.g. huge meta groups + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + | David | + And group "grp1" has been created + And group "grp2" has been created + And user "Brian" has been added to group "grp1" + And user "David" has been added to group "grp2" + + @skipOnOcis + Scenario Outline: user cannot share with a group that is excluded from receiving shares but can share with other groups + Given using OCS API version "" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + When user "Alice" shares file "fileToShare.txt" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + When user "Alice" shares folder "PARENT" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + When user "Alice" shares file "fileToShare.txt" with group "grp2" using the sharing API + And user "Alice" shares folder "PARENT" with group "grp2" using the sharing API + And user "David" accepts share "/fileToShare.txt" offered by user "Alice" using the sharing API + And user "David" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then as "David" file "/Shares/fileToShare.txt" should exist + And as "David" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + @skipOnOcis + Scenario Outline: exclude multiple groups from receiving shares stops the user to share with any of them + Given using OCS API version "" + And group "grp3" has been created + And user "Brian" has been added to group "grp3" + And the administrator has added group "grp1, grp2, grp3" to the exclude group from sharing list + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + And the administrator has added group "grp2" to the exclude groups from receiving shares list + And the administrator has added group "grp3" to the exclude groups from receiving shares list + When user "Alice" shares file "fileToShare.txt" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + When user "Alice" shares folder "PARENT" with group "grp2" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "David" that no shares are in the pending state + When user "Alice" shares folder "PARENT/CHILD" with group "grp3" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + @skipOnOcis + Scenario Outline: user cannot reshare a received share with a group that is excluded from receiving shares but can share with other groups + Given using OCS API version "" + And user "Carol" has created folder "PARENT" + And user "Carol" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Carol" has shared file "textfile0.txt" with user "Alice" + And user "Alice" has accepted share "/textfile0.txt" offered by user "Carol" + And user "Carol" has shared folder "PARENT" with user "Alice" + And user "Alice" has accepted share "/PARENT" offered by user "Carol" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + When user "Alice" shares file "/Shares/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + When user "Alice" shares folder "/Shares/PARENT" with group "grp1" using the sharing API + Then the OCS status code should be "403" + And the HTTP status code should be "" + And the OCS status message should be "The group is blacklisted for sharing" + And the sharing API should report to user "Brian" that no shares are in the pending state + When user "Alice" shares file "/Shares/textfile0.txt" with group "grp2" using the sharing API + And user "Alice" shares folder "/Shares/PARENT" with group "grp2" using the sharing API + And user "David" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "David" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then as "David" file "/Shares/textfile0.txt" should exist + And as "David" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 403 | + + + Scenario Outline: sharing with a user that is part of a group that is excluded from receiving shares still works + Given using OCS API version "" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + When user "Alice" shares file "fileToShare.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Alice" shares folder "PARENT" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Brian" accepts share "/fileToShare.txt" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then as "Brian" file "/Shares/fileToShare.txt" should exist + And as "Brian" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: sharing with a user that is part of a group that is excluded from receiving shares using an other group works + Given using OCS API version "" + And group "grp3" has been created + And user "Brian" has been added to group "grp3" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + When user "Alice" shares file "fileToShare.txt" with group "grp3" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Alice" shares folder "PARENT" with group "grp3" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Brian" accepts share "/fileToShare.txt" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then as "Brian" file "/Shares/fileToShare.txt" should exist + And as "Brian" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: a user that is part of a group that is excluded from receiving shares still can initiate shares + Given using OCS API version "" + And user "Brian" has created folder "PARENT" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And the administrator has added group "grp1" to the exclude groups from receiving shares list + When user "Brian" shares file "fileToShare.txt" with user "Carol" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" shares folder "PARENT" with user "Carol" using the sharing API + And the OCS status code should be "" + And the HTTP status code should be "200" + When user "Carol" accepts share "/fileToShare.txt" offered by user "Brian" using the sharing API + And user "Carol" accepts share "/PARENT" offered by user "Brian" using the sharing API + Then as "Carol" file "/Shares/fileToShare.txt" should exist + And as "Carol" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature b/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature new file mode 100644 index 00000000000..5c48f73ce70 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/acceptShares.feature @@ -0,0 +1,666 @@ +@api @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1328 +Feature: accept/decline shares coming from internal users + As a user + I want to have control of which received shares I accept + So that I can keep my file system clean + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And using new DAV path + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Brian" has created folder "PARENT" + And user "Brian" has created folder "FOLDER" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + @smokeTest + Scenario Outline: share a file & folder with another internal group when auto accept is disabled + Given user "Carol" has created folder "FOLDER" + And user "Carol" has created folder "PARENT" + And user "Carol" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + And user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + But user "Brian" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the pending state + | path | + | | + | | + And user "Carol" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + But user "Carol" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Carol" that these shares are in the pending state + | path | + | | + | | + @issue-ocis-2540 + Examples: + | pending_share_path_1 | pending_share_path_2 | + | /Shares/PARENT/ | /Shares/textfile0.txt | + + + Scenario Outline: share a file & folder with another internal user when auto accept is disabled + When user "Alice" shares folder "/PARENT" with user "Brian" using the sharing API + And user "Alice" shares file "/textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + But user "Brian" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the pending state + | path | + | | + | | + @issue-ocis-2540 + Examples: + | pending_share_path_1 | pending_share_path_2 | + | /Shares/PARENT/ | /Shares/textfile0.txt | + + @smokeTest @issue-ocis-2131 + Scenario: accept a pending share + Given user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | id | A_STRING | + | share_type | user | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | share,read,update | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | state | 0 | + | path | /Shares/textfile0.txt | + | item_type | file | + | mimetype | text/plain | + | storage_id | shared::/Shares/textfile0.txt | + | storage | A_STRING | + | item_source | A_STRING | + | file_source | A_STRING | + | file_target | /Shares/textfile0.txt | + | share_with | %username% | + | share_with_displayname | %displayname% | + | mail_send | 0 | + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the accepted state + | path | + | /Shares/PARENT | + | /Shares/textfile0.txt | + + @notToImplementOnOCIS + Scenario Outline: accept a pending share when there is a default folder for received shares + Given the administrator has set the default folder for received shares to "" + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | id | A_STRING | + | share_type | user | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | share,read,update | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | state | 0 | + | path | / | + | item_type | file | + | mimetype | text/plain | + | storage_id | shared::/ | + | storage | A_STRING | + | item_source | A_STRING | + | file_source | A_STRING | + | file_target | / | + | share_with | %username% | + | share_with_displayname | %displayname% | + | mail_send | 0 | + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | // | + | //parent.txt | + | /textfile0.txt | + | / | + And the sharing API should report to user "Brian" that these shares are in the accepted state + | path | + | // | + | / | + Examples: + | share_folder | top_folder | received_parent_name | received_textfile_name | + | | | PARENT (2) | textfile0 (2).txt | + | / | | PARENT (2) | textfile0 (2).txt | + | /ReceivedShares | /ReceivedShares | PARENT | textfile0.txt | + | ReceivedShares | /ReceivedShares | PARENT | textfile0.txt | + | /My/Received/Shares | /My/Received/Shares | PARENT | textfile0.txt | + + + Scenario: accept an accepted share + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + When user "Brian" accepts share "/shared" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And user "Brian" should see the following elements + | /Shares/shared/ | + And the sharing API should report to user "Brian" that these shares are in the accepted state + | path | + | /Shares/shared/ | + + @smokeTest + Scenario Outline: declines a pending share + Given user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" declines share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" declines share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + But user "Brian" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the declined state + | path | + | | + | | + @issue-ocis-2540 + Examples: + | declined_share_path_1 | declined_share_path_2 | + | /Shares/PARENT/ | /Shares/textfile0.txt | + + @smokeTest @issue-ocis-2128 + Scenario Outline: decline an accepted share + Given user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" declines share "/Shares/PARENT" offered by user "Alice" using the sharing API + And user "Brian" declines share "/Shares/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the declined state + | path | + | | + | | + @issue-ocis-2540 + Examples: + | declined_share_path_1 | declined_share_path_2 | + | /Shares/PARENT/ | /Shares/textfile0.txt | + + + Scenario: deleting shares in pending state + Given user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Alice" deletes folder "/PARENT" using the WebDAV API + And user "Alice" deletes file "/textfile0.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And the sharing API should report that no shares are shared with user "Brian" + + + Scenario Outline: only one user in a group accepts a share + Given user "Alice" has shared folder "/PARENT" with group "grp1" + And user "Alice" has shared file "/textfile0.txt" with group "grp1" + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Carol" that these shares are in the pending state + | path | + | | + | | + But user "Brian" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the accepted state + | path | + | /Shares/PARENT/ | + | /Shares/textfile0.txt | + @issue-ocis-2540 + Examples: + | pending_share_path_1 | pending_share_path_2 | + | /Shares/PARENT/ | /Shares/textfile0.txt | + + @issue-ocis-2131 + Scenario: receive two shares with identical names from different users, accept one by one + Given user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/Alice" + And user "Brian" has created folder "/shared" + And user "Brian" has created folder "/shared/Brian" + And user "Alice" has shared folder "/shared" with user "Carol" + And user "Brian" has shared folder "/shared" with user "Carol" + When user "Carol" accepts share "/shared" offered by user "Brian" using the sharing API + And user "Carol" accepts share "/shared" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should see the following elements + | /Shares/shared/Brian/ | + | /Shares/shared (2)/Alice/ | + And the sharing API should report to user "Carol" that these shares are in the accepted state + | path | + | /Shares/shared/ | + | /Shares/shared (2)/ | + + + Scenario Outline: share with a group that you are part of yourself + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the sharing API should report to user "Brian" that these shares are in the pending state + | path | + | | + And the sharing API should report that no shares are shared with user "Alice" + @issue-ocis-2540 + Examples: + | pending_share_path | + | /Shares/PARENT/ | + + + Scenario Outline: user accepts file that was initially accepted from another user and then declined + Given user "Alice" has uploaded file with content "First file" to "/testfile.txt" + And user "Brian" has uploaded file with content "Second file" to "/testfile.txt" + And user "Carol" has created folder "Shares" + And user "Carol" has uploaded file with content "Third file" to "/Shares/testfile.txt" + And user "Alice" has shared file "/testfile.txt" with user "Carol" + And user "Carol" has accepted share "/testfile.txt" offered by user "Alice" + When user "Carol" declines share "/Shares/testfile (2).txt" offered by user "Alice" using the sharing API + And user "Brian" shares file "/testfile.txt" with user "Carol" using the sharing API + And user "Carol" accepts share "/testfile.txt" offered by user "Brian" using the sharing API + And user "Carol" accepts share "" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And the sharing API should report to user "Carol" that these shares are in the accepted state + | path | + | /Shares/testfile (2).txt | + | /Shares/testfile (2) (2).txt | + And the content of file "/Shares/testfile.txt" for user "Carol" should be "Third file" + And the content of file "/Shares/testfile (2).txt" for user "Carol" should be "Second file" + And the content of file "/Shares/testfile (2) (2).txt" for user "Carol" should be "First file" + Examples: + | accepted_share_path | + | /testfile (2).txt | + + + Scenario Outline: user accepts shares received from multiple users with the same name when auto-accept share is disabled + Given user "David" has been created with default attributes and without skeleton files + And user "David" has created folder "PARENT" + And user "Brian" has shared folder "/PARENT" with user "Alice" + And user "Carol" has created folder "PARENT" + And user "Carol" has shared folder "/PARENT" with user "Alice" + And user "Alice" has created folder "Shares" + And user "Alice" has created folder "Shares/PARENT" + When user "Alice" accepts share "/PARENT" offered by user "Brian" using the sharing API + And user "Alice" declines share "/Shares/PARENT (2)" offered by user "Brian" using the sharing API + And user "Alice" accepts share "/PARENT" offered by user "Carol" using the sharing API + And user "Alice" accepts share "" offered by user "Brian" using the sharing API + And user "Alice" declines share "/Shares/PARENT (2)" offered by user "Carol" using the sharing API + And user "Alice" declines share "/Shares/PARENT (2) (2)" offered by user "Brian" using the sharing API + And user "David" shares folder "/PARENT" with user "Alice" using the sharing API + And user "Alice" accepts share "/PARENT" offered by user "David" using the sharing API + And user "Alice" accepts share "" offered by user "Carol" using the sharing API + And user "Alice" accepts share "" offered by user "Brian" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And the sharing API should report to user "Alice" that these shares are in the accepted state + | path | uid_owner | + | /Shares/PARENT (2)/ | David | + | /Shares/PARENT (2) (2)/ | Carol | + | /Shares/PARENT (2) (2) (2)/ | Brian | + Examples: + | accepted_share_path_1 | accepted_share_path_2 | + | /PARENT (2) | /PARENT (2) (2) | + + + Scenario: user shares folder with matching folder-name for both user involved in sharing + Given user "Alice" has uploaded file with content "uploaded content" to "/PARENT/abc.txt" + And user "Alice" has uploaded file with content "uploaded content" to "/FOLDER/abc.txt" + When user "Alice" shares folder "/PARENT" with user "Brian" using the sharing API + And user "Alice" shares folder "/FOLDER" with user "Brian" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/PARENT/abc.txt | + | /Shares/FOLDER/ | + | /Shares/FOLDER/abc.txt | + And user "Brian" should not see the following elements + | /FOLDER/abc.txt | + | /PARENT/abc.txt | + And the content of file "/Shares/PARENT/abc.txt" for user "Brian" should be "uploaded content" + And the content of file "/Shares/FOLDER/abc.txt" for user "Brian" should be "uploaded content" + + + Scenario: user shares folder in a group with matching folder-name for every users involved + Given user "Alice" has uploaded file with content "uploaded content" to "/PARENT/abc.txt" + And user "Alice" has uploaded file with content "uploaded content" to "/FOLDER/abc.txt" + And user "Carol" has created folder "PARENT" + And user "Carol" has created folder "FOLDER" + When user "Alice" shares folder "/PARENT" with group "grp1" using the sharing API + And user "Alice" shares folder "/FOLDER" with group "grp1" using the sharing API + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/FOLDER" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/PARENT" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/FOLDER" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/FOLDER/ | + | /Shares/PARENT/abc.txt | + | /Shares/FOLDER/abc.txt | + And user "Brian" should not see the following elements + | /FOLDER/abc.txt | + | /PARENT/abc.txt | + And user "Carol" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/FOLDER/ | + | /Shares/PARENT/abc.txt | + | /Shares/FOLDER/abc.txt | + And user "Carol" should not see the following elements + | /FOLDER/abc.txt | + | /PARENT/abc.txt | + And the content of file "/Shares/PARENT/abc.txt" for user "Brian" should be "uploaded content" + And the content of file "/Shares/FOLDER/abc.txt" for user "Brian" should be "uploaded content" + And the content of file "/Shares/PARENT/abc.txt" for user "Carol" should be "uploaded content" + And the content of file "/Shares/FOLDER/abc.txt" for user "Carol" should be "uploaded content" + + + Scenario: user shares files in a group with matching file-names for every users involved in sharing + Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile1.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "textfile1.txt" + And user "Carol" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Carol" has uploaded file "filesForUpload/textfile.txt" to "textfile1.txt" + When user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + And user "Alice" shares file "/textfile1.txt" with group "grp1" using the sharing API + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/textfile1.txt" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/textfile1.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /textfile0.txt | + | /textfile1.txt | + | /Shares/textfile0.txt | + | /Shares/textfile1.txt | + And user "Carol" should see the following elements + | /textfile0.txt | + | /textfile1.txt | + | /Shares/textfile0.txt | + | /Shares/textfile1.txt | + + + Scenario: user shares resource with matching resource-name with another user when auto accept is disabled + When user "Alice" shares folder "/PARENT" with user "Brian" using the sharing API + And user "Alice" shares file "/textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /PARENT/ | + | /textfile0.txt | + But user "Brian" should not see the following elements + | /Shares/textfile0.txt | + | /Shares/PARENT/ | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /PARENT/ | + | /textfile0.txt | + | /Shares/PARENT/ | + | /Shares/textfile0.txt | + + + Scenario: user shares file in a group with matching filename when auto accept is disabled + Given user "Carol" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + When user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And user "Brian" should see the following elements + | /textfile0.txt | + But user "Brian" should not see the following elements + | /Shares/textfile0.txt | + And user "Carol" should see the following elements + | /textfile0.txt | + But user "Carol" should not see the following elements + | /Shares/textfile0.txt | + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + When user "Carol" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /textfile0.txt | + | /Shares/textfile0.txt | + And user "Carol" should see the following elements + | /textfile0.txt | + | /Shares/textfile0.txt | + + @skipOnLDAP + Scenario: user shares folder with matching folder name to a user before that user has logged in + Given these users have been created without skeleton files and not initialized: + | username | + | David | + And user "Alice" has uploaded file with content "uploaded content" to "/PARENT/abc.txt" + When user "Alice" shares folder "/PARENT" with user "David" using the sharing API + And user "David" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "David" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/abc.txt | + And user "David" should not see the following elements + | /PARENT (2)/ | + And the content of file "/Shares/PARENT/abc.txt" for user "David" should be "uploaded content" + + @issue-ocis-1123 + Scenario Outline: deleting a share accepted file and folder + Given user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" deletes file "/Shares/PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And the sharing API should report to user "Brian" that these shares are in the declined state + | path | + | | + @issue-ocis-2540 + Examples: + | declined_share_path | + | /Shares/PARENT | + + @issue-ocis-765 @issue-ocis-2131 + Scenario: shares exist after restoring already shared file to a previous version + And user "Alice" has uploaded file with content "Test Content." to "/toShareFile.txt" + And user "Alice" has uploaded file with content "Content Test Updated." to "/toShareFile.txt" + And user "Alice" has shared file "/toShareFile.txt" with user "Brian" + And user "Brian" has accepted share "/toShareFile.txt" offered by user "Alice" + When user "Alice" restores version index "1" of file "/toShareFile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/toShareFile.txt" for user "Alice" should be "Test Content." + And the content of file "/Shares/toShareFile.txt" for user "Brian" should be "Test Content." + + @issue-ocis-2131 + Scenario: a user receives multiple group shares for matching file and folder name + Given group "grp2" has been created + And user "Alice" has been added to group "grp2" + And user "Brian" has been added to group "grp2" + And user "Carol" has created folder "/PARENT" + And user "Alice" has created folder "/PaRent" + And user "Alice" has uploaded the following files with content "subfile, from alice to grp2" + | path | + | /PARENT/parent.txt | + | /PaRent/parent.txt | + And user "Alice" has uploaded the following files with content "from alice to grp2" + | path | + | /PARENT.txt | + And user "Carol" has uploaded the following files with content "subfile, from carol to grp1" + | path | + | /PARENT/parent.txt | + And user "Carol" has uploaded the following files with content "from carol to grp1" + | path | + | /PARENT.txt | + | /parent.txt | + When user "Alice" shares the following entries with group "grp2" using the sharing API + | path | + | /PARENT | + | /PaRent | + | /PARENT.txt | + And user "Brian" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /PARENT | + | /PaRent | + | /PARENT.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/PaRent/ | + | /Shares/PARENT.txt | + And the content of file "/Shares/PARENT/parent.txt" for user "Brian" should be "subfile, from alice to grp2" + And the content of file "/Shares/PaRent/parent.txt" for user "Brian" should be "subfile, from alice to grp2" + And the content of file "/Shares/PARENT.txt" for user "Brian" should be "from alice to grp2" + When user "Carol" shares the following entries with group "grp2" using the sharing API + | path | + | /PARENT | + | /PARENT.txt | + | /parent.txt | + And user "Brian" accepts the following shares offered by user "Carol" using the sharing API + | path | + | /PARENT | + | /PARENT.txt | + | /parent.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/PARENT (2)/ | + | /Shares/PaRent/ | + | /Shares/PARENT.txt | + | /Shares/PARENT (2).txt | + | /Shares/parent.txt | + And the content of file "/Shares/PARENT (2)/parent.txt" for user "Brian" should be "subfile, from carol to grp1" + And the content of file "/Shares/PARENT (2).txt" for user "Brian" should be "from carol to grp1" + And the content of file "/Shares/parent.txt" for user "Brian" should be "from carol to grp1" + + @issue-ocis-2131 + Scenario: a group receives multiple shares from non-member for matching file and folder name + Given user "Brian" has been removed from group "grp1" + And user "Alice" has created folder "/PaRent" + And user "Carol" has created folder "/PARENT" + And user "Alice" has uploaded the following files with content "subfile, from alice to grp1" + | path | + | /PARENT/parent.txt | + | /PaRent/parent.txt | + And user "Alice" has uploaded the following files with content "from alice to grp1" + | path | + | /PARENT.txt | + And user "Brian" has uploaded the following files with content "subfile, from brian to grp1" + | path | + | /PARENT/parent.txt | + And user "Brian" has uploaded the following files with content "from brian to grp1" + | path | + | /PARENT.txt | + | /parent.txt | + When user "Alice" shares the following entries with group "grp1" using the sharing API + | path | + | /PARENT | + | /PaRent | + | /PARENT.txt | + And user "Carol" accepts the following shares offered by user "Alice" using the sharing API + | path | + | /PARENT | + | /PaRent | + | /PARENT.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should see the following elements + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/PaRent/ | + | /Shares/PARENT.txt | + And the content of file "/Shares/PARENT/parent.txt" for user "Carol" should be "subfile, from alice to grp1" + And the content of file "/Shares/PARENT.txt" for user "Carol" should be "from alice to grp1" + When user "Brian" shares the following entries with group "grp1" using the sharing API + | path | + | /PARENT | + | /PARENT.txt | + | /parent.txt | + And user "Carol" accepts the following shares offered by user "Brian" using the sharing API + | path | + | /PARENT | + | /PARENT.txt | + | /parent.txt | + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Carol" should see the following elements + | /PARENT/ | + | /Shares/PARENT/ | + | /Shares/PARENT (2)/ | + | /Shares/PaRent/ | + | /Shares/PARENT.txt | + | /Shares/PARENT (2).txt | + | /Shares/parent.txt | + And the content of file "/Shares/PARENT (2)/parent.txt" for user "Carol" should be "subfile, from brian to grp1" + And the content of file "/Shares/PARENT (2).txt" for user "Carol" should be "from brian to grp1" diff --git a/tests/acceptance/features/coreApiShareManagementToShares/acceptSharesToSharesFolder.feature b/tests/acceptance/features/coreApiShareManagementToShares/acceptSharesToSharesFolder.feature new file mode 100644 index 00000000000..ab1dbe33750 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/acceptSharesToSharesFolder.feature @@ -0,0 +1,75 @@ +@api @files_sharing-app-required +Feature: accept/decline shares coming from internal users to the Shares folder + As a user + I want to have control of which received shares I accept + So that I can keep my file system clean + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using OCS API version "1" + And using new DAV path + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario: When accepting a share of a file, the received file is accessible + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + + + Scenario: When accepting a share of a folder, the received folder is accessible + Given user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has shared file "/PARENT" with user "Brian" + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the content of file "/Shares/PARENT/parent.txt" for user "Brian" should be "ownCloud test text file parent" + + + Scenario: When accepting a share of a file, the response is valid + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + When user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/textfile0.txt | + | path | /Shares/textfile0.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + + + Scenario: When accepting a share of a folder, the response is valid + Given user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has shared file "/PARENT" with user "Brian" + When user "Brian" accepts share "/PARENT" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/PARENT | + | path | /Shares/PARENT | + | permissions | all | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | folder | + | mimetype | httpd/unix-directory | + | storage_id | ANY_VALUE | + | share_type | user | + And the content of file "/Shares/PARENT/parent.txt" for user "Brian" should be "ownCloud test text file parent" diff --git a/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature b/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature new file mode 100644 index 00000000000..a28b21edd25 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/mergeShare.feature @@ -0,0 +1,158 @@ +@api @files_sharing-app-required @issue-ocis-reva-1328 @issues-ocis-1289 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + + @smokeTest + Scenario: Merging shares for recipient when shared from outside with group and member + Given user "Alice" has created folder "/merge-test-outside" + When user "Alice" shares folder "/merge-test-outside" with group "grp1" using the sharing API + And user "Alice" shares folder "/merge-test-outside" with user "Brian" using the sharing API + And user "Brian" accepts share "/merge-test-outside" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/merge-test-outside" should exist + And as "Brian" folder "/Shares/merge-test-outside (2)" should not exist + + + Scenario: Merging shares for recipient when shared from outside with group and member with different permissions + Given user "Alice" has created folder "/merge-test-outside-perms" + When user "Alice" shares folder "/merge-test-outside-perms" with group "grp1" with permissions "read" using the sharing API + And user "Alice" shares folder "/merge-test-outside-perms" with user "Brian" with permissions "all" using the sharing API + And user "Brian" accepts share "/merge-test-outside-perms" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as user "Brian" folder "/Shares/merge-test-outside-perms" should contain a property "oc:permissions" with value "SRDNVCK" + And as "Brian" folder "/Shares/merge-test-outside-perms (2)" should not exist + + + Scenario: Merging shares for recipient when shared from outside with two groups + Given group "grp2" has been created + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "/merge-test-outside-twogroups" + When user "Alice" shares folder "/merge-test-outside-twogroups" with group "grp1" using the sharing API + And user "Alice" shares folder "/merge-test-outside-twogroups" with group "grp2" using the sharing API + And user "Brian" accepts share "/merge-test-outside-twogroups" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/merge-test-outside-twogroups" should exist + And as "Brian" folder "/Shares/merge-test-outside-twogroups (2)" should not exist + + + Scenario: Merging shares for recipient when shared from outside with two groups with different permissions + Given group "grp2" has been created + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "/merge-test-outside-twogroups-perms" + When user "Alice" shares folder "/merge-test-outside-twogroups-perms" with group "grp1" with permissions "read" using the sharing API + And user "Alice" shares folder "/merge-test-outside-twogroups-perms" with group "grp2" with permissions "all" using the sharing API + And user "Brian" accepts share "/merge-test-outside-twogroups-perms" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as user "Brian" folder "/Shares/merge-test-outside-twogroups-perms" should contain a property "oc:permissions" with value "SRDNVCK" + And as "Brian" folder "/Shares/merge-test-outside-twogroups-perms (2)" should not exist + + + Scenario: Merging shares for recipient when shared from outside with two groups and member + Given group "grp2" has been created + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "/merge-test-outside-twogroups-member-perms" + When user "Alice" shares folder "/merge-test-outside-twogroups-member-perms" with group "grp1" with permissions "read" using the sharing API + And user "Alice" shares folder "/merge-test-outside-twogroups-member-perms" with group "grp2" with permissions "all" using the sharing API + And user "Alice" shares folder "/merge-test-outside-twogroups-member-perms" with user "Brian" with permissions "read" using the sharing API + And user "Brian" accepts share "/merge-test-outside-twogroups-member-perms" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as user "Brian" folder "/Shares/merge-test-outside-twogroups-member-perms" should contain a property "oc:permissions" with value "SRDNVCK" + And as "Brian" folder "/Shares/merge-test-outside-twogroups-member-perms (2)" should not exist + + + Scenario: Merging shares for recipient when shared from inside with group + Given user "Brian" has created folder "/merge-test-inside-group" + When user "Brian" shares folder "/merge-test-inside-group" with group "grp1" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And as "Brian" folder "/merge-test-inside-group" should exist + And as "Brian" folder "/Shares/merge-test-inside-group" should not exist + + + Scenario: Merging shares for recipient when shared from inside with two groups + Given group "grp2" has been created + And user "Brian" has been added to group "grp2" + And user "Brian" has created folder "/merge-test-inside-twogroups" + When user "Brian" shares folder "/merge-test-inside-twogroups" with group "grp1" using the sharing API + And user "Brian" shares folder "/merge-test-inside-twogroups" with group "grp2" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/merge-test-inside-twogroups" should exist + And as "Brian" folder "/Shares/merge-test-inside-twogroups" should not exist + And as "Brian" folder "/Shares/merge-test-inside-twogroups (2)" should not exist + + + Scenario Outline: Merging shares for recipient when shared from inside with group with less permissions + Given group "grp2" has been created + And user "Brian" has been added to group "grp2" + And user "Brian" has created folder "/merge-test-inside-twogroups-perms" + When user "Brian" shares folder "/merge-test-inside-twogroups-perms" with group "grp1" using the sharing API + And user "Brian" shares folder "/merge-test-inside-twogroups-perms" with group "grp2" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as user "Brian" folder "/merge-test-inside-twogroups-perms" should contain a property "oc:permissions" with value "" or with value "" + And as "Brian" folder "/Shares/merge-test-inside-twogroups-perms" should not exist + And as "Brian" folder "/Shares/merge-test-inside-twogroups-perms (2)" should not exist + @skipOnOcV10 + Examples: + | expected_permission_1 | expected_permission_2 | + | RDNVCKZ | RMDNVCKZ | + @skipOnOcis + Examples: + | expected_permission_1 | expected_permission_2 | + | RDNVCK | RMDNVCK | + + + Scenario: Merging shares for recipient when shared from outside with group then user and recipient renames in between + Given user "Alice" has created folder "/merge-test-outside-groups-renamebeforesecondshare" + # Section 1: Brian receives and accepts the group share from Alice and moves and renames it out of the "Shares" folder + When user "Alice" shares folder "/merge-test-outside-groups-renamebeforesecondshare" with group "grp1" using the sharing API + And user "Brian" accepts share "/merge-test-outside-groups-renamebeforesecondshare" offered by user "Alice" using the sharing API + And user "Brian" moves folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" to "/merge-test-outside-groups-renamebeforesecondshare-renamed" using the WebDAV API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on each endpoint should be "200, 200, 201" respectively + And as "Brian" folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" should not exist + But as "Brian" folder "/merge-test-outside-groups-renamebeforesecondshare-renamed" should exist + # Section 2: Brian receives and accepts the user share from Alice. Brian now has 2 shares of the same folder owned by Alice + # The server "merges" the 2 shares and presents them to Brian as a single folder inside the "Shares" folder + When user "Alice" shares folder "/merge-test-outside-groups-renamebeforesecondshare" with user "Brian" using the sharing API + And user "Brian" accepts share "/merge-test-outside-groups-renamebeforesecondshare" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" should exist + But as "Brian" folder "/merge-test-outside-groups-renamebeforesecondshare-renamed" should not exist + + + Scenario: Merging shares for recipient when shared from outside with user then group and recipient renames in between + Given user "Alice" has created folder "/merge-test-outside-groups-renamebeforesecondshare" + # Section 1: Brian receives and accepts the user share from Alice and moves and renames it out of the "Shares" folder + When user "Alice" shares folder "/merge-test-outside-groups-renamebeforesecondshare" with user "Brian" using the sharing API + And user "Brian" accepts share "/merge-test-outside-groups-renamebeforesecondshare" offered by user "Alice" using the sharing API + And user "Brian" moves folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" to "/merge-test-outside-groups-renamebeforesecondshare-renamed" using the WebDAV API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on each endpoint should be "200, 200, 201" respectively + And as "Brian" folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" should not exist + But as "Brian" folder "/merge-test-outside-groups-renamebeforesecondshare-renamed" should exist + # Section 2: Brian receives and accepts the group share from Alice. Brian now has 2 shares of the same folder owned by Alice + # The server "merges" the 2 shares and presents them to Brian as a single folder inside the "Shares" folder + When user "Alice" shares folder "/merge-test-outside-groups-renamebeforesecondshare" with group "grp1" using the sharing API + And user "Brian" accepts share "/merge-test-outside-groups-renamebeforesecondshare" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares/merge-test-outside-groups-renamebeforesecondshare" should exist + But as "Brian" folder "/merge-test-outside-groups-renamebeforesecondshare-renamed" should not exist diff --git a/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature new file mode 100644 index 00000000000..6f31015a0ba --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature @@ -0,0 +1,376 @@ +@api @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1328 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: Keep usergroup shares (#22143) + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/TMP" + When user "Alice" shares folder "TMP" with group "grp1" using the sharing API + And user "Brian" accepts share "/TMP" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/TMP" offered by user "Alice" using the sharing API + And user "Brian" creates folder "/myFOLDER" using the WebDAV API + And user "Brian" moves folder "/Shares/TMP" to "/myFOLDER/myTMP" using the WebDAV API + And the administrator deletes user "Carol" using the provisioning API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /myFOLDER/myTMP/ | + + + Scenario: Keep usergroup shares when the user renames the share within the Shares folder(#22143) + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/TMP" + When user "Alice" shares folder "TMP" with group "grp1" using the sharing API + And user "Brian" accepts share "/TMP" offered by user "Alice" using the sharing API + And user "Carol" accepts share "/TMP" offered by user "Alice" using the sharing API + And user "Brian" moves folder "/Shares/TMP" to "/Shares/new" using the WebDAV API + And the administrator deletes user "Carol" using the provisioning API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And user "Brian" should see the following elements + | /Shares/new/| + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: keep user shared file name same after one of recipient has renamed the file + Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" + And user "Alice" has shared file "/sharefile.txt" with user "Brian" + And user "Alice" has shared file "/sharefile.txt" with user "Carol" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Carol" has accepted share "/sharefile.txt" offered by user "Alice" + When user "Carol" moves file "/Shares/sharefile.txt" to "/renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "/renamedsharefile.txt" should exist + And as "Alice" file "/sharefile.txt" should exist + And as "Brian" file "/Shares/sharefile.txt" should exist + + + Scenario: keep user shared file name same after one of recipient has renamed the file inside the Shares folder + Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" + And user "Alice" has shared file "/sharefile.txt" with user "Brian" + And user "Alice" has shared file "/sharefile.txt" with user "Carol" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Carol" has accepted share "/sharefile.txt" offered by user "Alice" + When user "Carol" moves file "/Shares/sharefile.txt" to "/Shares/renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "/Shares/renamedsharefile.txt" should exist + And as "Alice" file "/sharefile.txt" should exist + And as "Brian" file "/Shares/sharefile.txt" should exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: keep user shared file directory same in respect to respective user if one of the recipient has moved the file + Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" + And user "Alice" has shared file "/sharefile.txt" with user "Brian" + And user "Alice" has shared file "/sharefile.txt" with user "Carol" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Carol" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Carol" has created folder "newfolder" + When user "Carol" moves file "/Shares/sharefile.txt" to "/newfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "/newfolder/sharefile.txt" should exist + And as "Alice" file "/sharefile.txt" should exist + And as "Brian" file "/Shares/sharefile.txt" should exist + + @issue-ocis-2146 @notToImplementOnOCIS + Scenario Outline: move folder inside received folder with special characters + Given group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "" + And user "Alice" has created folder "" + And user "Brian" has created folder "" + And user "Carol" has created folder "" + And user "Alice" has shared folder "" with user "Brian" + And user "Brian" has accepted share "/" offered by user "Alice" + When user "Brian" moves folder "" to "/Shares//" using the WebDAV API + And user "Alice" shares folder "" with group "grp1" using the sharing API + And user "Carol" accepts share "/" offered by user "Alice" using the sharing API + And user "Carol" moves folder "/" to "/Shares//" using the WebDAV API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on each endpoint should be "201, 200, 200, 201" respectively + And as "Alice" folder "/" should exist + And as "Brian" folder "/Shares//" should exist + And as "Alice" folder "/" should exist + And as "Carol" folder "/Shares//" should exist + Examples: + | sharer_folder | group_folder | receiver_folder | + | ?abc=oc # | ?abc=oc g%rp# | # oc?test=oc&a | + | @a#8a=b?c=d | @a#8a=b?c=d grp | ?a#8 a=b?c=d | + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver renames a received share with share, read, change permissions + Given user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "/folderToShare/fileInside" + And user "Alice" has shared folder "folderToShare" with user "Brian" with permissions "share,read,change" + And user "Brian" has accepted share "/folderToShare" offered by user "Alice" + When user "Brian" moves folder "/Shares/folderToShare" to "myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "myFolder" should exist + But as "Alice" folder "myFolder" should not exist + When user "Brian" moves file "/myFolder/fileInside" to "/myFolder/renamedFile" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/myFolder/renamedFile" should exist + And as "Alice" file "/folderToShare/renamedFile" should exist + But as "Alice" file "/folderToShare/fileInside" should not exist + + + Scenario: receiver renames a received share with share, read, change permissions inside the Shares folder + Given user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "/folderToShare/fileInside" + And user "Alice" has shared folder "folderToShare" with user "Brian" with permissions "share,read,change" + And user "Brian" has accepted share "/folderToShare" offered by user "Alice" + When user "Brian" moves folder "/Shares/folderToShare" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "/Shares/myFolder" should not exist + When user "Brian" moves file "/Shares/myFolder/fileInside" to "/Shares/myFolder/renamedFile" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/myFolder/renamedFile" should exist + And as "Alice" file "/folderToShare/renamedFile" should exist + But as "Alice" file "/folderToShare/fileInside" should not exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver tries to rename a received share with share, read permissions + Given user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "/folderToShare/fileInside" + And user "Alice" has shared folder "folderToShare" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/folderToShare" offered by user "Alice" + When user "Brian" moves folder "/Shares/folderToShare" to "/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "myFolder" should exist + But as "Alice" folder "myFolder" should not exist + When user "Brian" moves file "/myFolder/fileInside" to "/myFolder/renamedFile" using the WebDAV API + Then the HTTP status code should be "403" + And as "Brian" file "/myFolder/renamedFile" should not exist + But as "Brian" file "/myFolder/fileInside" should exist + + + Scenario: receiver tries to rename a received share with share, read permissions inside the Shares folder + Given user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "/folderToShare/fileInside" + And user "Alice" has shared folder "folderToShare" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/folderToShare" offered by user "Alice" + When user "Brian" moves folder "/Shares/folderToShare" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "/Shares/myFolder" should not exist + When user "Brian" moves file "/Shares/myFolder/fileInside" to "/Shares/myFolder/renamedFile" using the WebDAV API + Then the HTTP status code should be "403" + And as "Brian" file "/Shares/myFolder/renamedFile" should not exist + But as "Brian" file "Shares/myFolder/fileInside" should exist + + + Scenario: receiver renames a received folder share to a different name on the same folder + Given user "Alice" has created folder "PARENT" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "myFolder" should not exist + + + Scenario: receiver renames a received file share to different name on the same folder + Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves file "/Shares/fileToShare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Alice" file "newFile.txt" should not exist + + + Scenario: receiver renames a received file share to different name on the same folder for group sharing + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves file "/Shares/fileToShare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Alice" file "newFile.txt" should not exist + + + Scenario: receiver renames a received folder share to different name on the same folder for group sharing + Given group "grp1" has been created + And user "Alice" has created folder "PARENT" + And user "Brian" has been added to group "grp1" + And user "Alice" has shared folder "PARENT" with group "grp1" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "myFolder" should not exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver renames a received file share with read,update,share permissions in group sharing + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with group "grp1" with permissions "read,update,share" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves folder "/Shares/fileToShare.txt" to "newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "newFile.txt" should exist + But as "Alice" file "newFile.txt" should not exist + + + Scenario: receiver renames a received file share with read,update,share permissions inside the Shares folder in group sharing + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with group "grp1" with permissions "read,update,share" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves folder "/Shares/fileToShare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Alice" file "/Shares/newFile.txt" should not exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver renames a received folder share with share, read, change permissions in group sharing + Given group "grp1" has been created + And user "Alice" has created folder "PARENT" + And user "Brian" has been added to group "grp1" + And user "Alice" has shared folder "PARENT" with group "grp1" with permissions "share,read,change" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "myFolder" should exist + But as "Alice" folder "myFolder" should not exist + + + Scenario: receiver renames a received folder share with share, read, change permissions inside the Shares folder in group sharing + Given group "grp1" has been created + And user "Alice" has created folder "PARENT" + And user "Brian" has been added to group "grp1" + And user "Alice" has shared folder "PARENT" with group "grp1" with permissions "share,read,change" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "/Shares/myFolder" should not exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver renames a received file share with share, read permissions in group sharing + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with group "grp1" with permissions "share,read" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves file "/Shares/fileToShare.txt" to "/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "newFile.txt" should exist + But as "Alice" file "newFile.txt" should not exist + + + Scenario: receiver renames a received file share with share, read permissions inside the Shares folder in group sharing) + Given group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToShare.txt" + And user "Alice" has shared file "fileToShare.txt" with group "grp1" with permissions "share,read" + And user "Brian" has accepted share "/fileToShare.txt" offered by user "Alice" + When user "Brian" moves file "/Shares/fileToShare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Alice" file "/Shares/newFile.txt" should not exist + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver renames a received folder share with share, read permissions in group sharing + Given group "grp1" has been created + And user "Alice" has created folder "PARENT" + And user "Brian" has been added to group "grp1" + And user "Alice" has shared folder "PARENT" with group "grp1" with permissions "share,read" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "myFolder" should exist + But as "Alice" folder "myFolder" should not exist + + + Scenario: receiver renames a received folder share with share, read permissions inside the Shares folder in group sharing + Given group "grp1" has been created + And user "Alice" has created folder "PARENT" + And user "Brian" has been added to group "grp1" + And user "Alice" has shared folder "PARENT" with group "grp1" with permissions "share,read" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" moves folder "/Shares/PARENT" to "/Shares/myFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/myFolder" should exist + But as "Alice" folder "/Shares/myFolder" should not exist + + @issue-ocis-2141 + Scenario Outline: receiver renames a received folder share to name with special characters in group sharing + Given group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "" + And user "Alice" has created folder "" + And user "Alice" has shared folder "" with user "Brian" + And user "Brian" has accepted share "/" offered by user "Alice" + When user "Brian" moves folder "/Shares/" to "/Shares/" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "" should not exist + And as "Brian" folder "/Shares/" should exist + When user "Alice" shares folder "" with group "grp1" using the sharing API + And user "Carol" accepts share "/" offered by user "Alice" using the sharing API + And user "Carol" moves folder "/Shares/" to "/Shares/" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "" should not exist + But as "Carol" folder "/Shares/" should exist + Examples: + | sharer_folder | group_folder | receiver_folder | + | ?abc=oc # | ?abc=oc g%rp# | # oc?test=oc&a | + | @a#8a=b?c=d | @a#8a=b?c=d grp | ?a#8 a=b?c=d | + + @issue-ocis-2141 + Scenario Outline: receiver renames a received file share to name with special characters with share, read, change permissions in group sharing + Given group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "" + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "//fileInside" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "//fileInside" + And user "Alice" has shared folder "" with user "Brian" with permissions "share,read,change" + And user "Brian" has accepted share "/" offered by user "Alice" + When user "Brian" moves folder "/Shares//fileInside" to "/Shares//" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And as "Brian" file "/Shares//" should exist + When user "Alice" shares folder "" with group "grp1" with permissions "share,read,change" using the sharing API + And user "Carol" accepts share "/" offered by user "Alice" using the sharing API + And user "Carol" moves folder "/Shares//fileInside" to "/Shares//" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And as "Carol" file "/Shares//" should exist + Examples: + | sharer_folder | group_folder | receiver_file | + | ?abc=oc # | ?abc=oc g%rp# | # oc?test=oc&a | + | @a#8a=b?c=d | @a#8a=b?c=d grp | ?a#8 a=b?c=d | + + @issue-ocis-2141 @notToImplementOnOCIS + Scenario: receiver moves file within a received folder to new folder + Given user "Alice" has created folder "folderToShare" + And user "Brian" has created folder "FOLDER" + And user "Alice" has uploaded file with content "thisIsAFileInsideTheSharedFolder" to "/folderToShare/fileToShare1.txt" + And user "Alice" has uploaded file with content "thisIsAnotherFileInsideTheSharedFolder" to "/folderToShare/fileToShare2.txt" + And user "Alice" has shared folder "folderToShare" with user "Brian" with permissions "share,read,change" + And user "Brian" has accepted share "/folderToShare" offered by user "Alice" + When user "Brian" moves file "/Shares/folderToShare/fileToShare1.txt" to "/FOLDER/fileToShare1.txt" using the WebDAV API + And user "Brian" moves file "/Shares/folderToShare/fileToShare2.txt" to "/FOLDER/fileToShare2.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + And as "Brian" file "/FOLDER/fileToShare1.txt" should exist + And as "Brian" file "/FOLDER/fileToShare2.txt" should exist + But as "Alice" file "/folderToShare/fileToShare1.txt" should not exist + And as "Alice" file "/folderToShare/fileToShare2.txt" should not exist diff --git a/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShareFromLocalStorage.feature b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShareFromLocalStorage.feature new file mode 100644 index 00000000000..59207f03c29 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShareFromLocalStorage.feature @@ -0,0 +1,90 @@ +@api @local_storage @files_external-app-required @notToImplementOnOCIS +Feature: local-storage + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @skipOnEncryptionType:user-keys @encryption-issue-181 + Scenario Outline: receiver renames a received file share from local storage + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/local_storage/filetoshare.txt" + And user "Alice" has shared file "/local_storage/filetoshare.txt" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" moves file "/Shares/filetoshare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Brian" file "/Shares/filetoshare.txt" should not exist + And as "Alice" file "/local_storage/filetoshare.txt" should exist + But as "Alice" file "/local_storage/newFile.txt" should not exist + Examples: + | ocs_api_version | pending_share_path | + | 1 | /filetoshare.txt | + | 2 | /filetoshare.txt | + + + Scenario Outline: receiver renames a received folder share from local storage + Given using OCS API version "" + And user "Alice" has created folder "/local_storage/foo" + And user "Alice" has shared folder "/local_storage/foo" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" moves folder "/Shares/foo" to "/Shares/newFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/newFolder" should exist + But as "Brian" folder "/Shares/foo" should not exist + And as "Alice" folder "/local_storage/foo" should exist + But as "Alice" folder "/local_storage/newFolder" should not exist + Examples: + | ocs_api_version | pending_share_path | + | 1 | /foo | + | 2 | /foo | + + @skipOnEncryptionType:user-keys @encryption-issue-181 + Scenario Outline: sub-folders,file inside a renamed received folder shared from local storage are accessible + Given using OCS API version "" + And user "Alice" has created folder "/local_storage/foo" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/local_storage/foo/filetoshare.txt" + And user "Alice" has created folder "/local_storage/foo/folder1" + And user "Alice" has created folder "/local_storage/foo/folder2" + And user "Alice" has created folder "/local_storage/foo/folder2/subfolder" + And user "Alice" has shared folder "/local_storage/foo" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" moves folder "/Shares/foo" to "/Shares/newFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/newFolder" should exist + And as "Brian" file "/Shares/newFolder/filetoshare.txt" should exist + And as "Brian" folder "/Shares/newFolder/folder1" should exist + And as "Brian" folder "/Shares/newFolder/folder2" should exist + And as "Brian" folder "/Shares/newFolder/folder2/subfolder" should exist + But as "Brian" folder "/Shares/foo" should not exist + And as "Alice" folder "/local_storage/foo" should exist + But as "Alice" folder "/local_storage/newFolder" should not exist + Examples: + | ocs_api_version | pending_share_path | + | 1 | /foo | + | 2 | /foo | + + @skipOnEncryptionType:user-keys @encryption-issue-181 + Scenario Outline: receiver renames a received file share from local storage in group sharing + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/local_storage/filetoshare.txt" + And user "Alice" has shared file "/local_storage/filetoshare.txt" with group "grp1" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" moves file "/Shares/filetoshare.txt" to "/Shares/newFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/newFile.txt" should exist + But as "Brian" file "/Shares/filetoshare.txt" should not exist + And as "Alice" file "/local_storage/filetoshare.txt" should exist + But as "Alice" file "/local_storage/newFile.txt" should not exist + Examples: + | ocs_api_version | pending_share_path | + | 1 | /filetoshare.txt | + | 2 | /filetoshare.txt | + \ No newline at end of file diff --git a/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature b/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature new file mode 100644 index 00000000000..1a9528e3b63 --- /dev/null +++ b/tests/acceptance/features/coreApiShareManagementToShares/moveShareInsideAnotherShare.feature @@ -0,0 +1,107 @@ +@api @files_sharing-app-required +Feature: moving a share inside another share + As a user + I want to move a shared resource inside another shared resource + Because I need full flexibility when managing resources. + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "folderA" + And user "Alice" has created folder "folderB" + And user "Alice" has uploaded file with content "text A" to "/folderA/fileA.txt" + And user "Alice" has uploaded file with content "text B" to "/folderB/fileB.txt" + And user "Alice" has shared folder "folderA" with user "Brian" + And user "Alice" has shared folder "folderB" with user "Brian" + And user "Brian" has accepted share "/folderA" offered by user "Alice" + And user "Brian" has accepted share "/folderB" offered by user "Alice" + + + Scenario: share receiver cannot move a whole share inside another share + When user "Brian" moves folder "Shares/folderB" to "Shares/folderA/folderB" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/folderB" should exist + And as "Brian" folder "/Shares/folderB" should exist + And as "Alice" file "/folderB/fileB.txt" should exist + And as "Brian" file "/Shares/folderB/fileB.txt" should exist + + + Scenario: share owner moves a whole share inside another share + When user "Alice" moves folder "folderB" to "folderA/folderB" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "/folderB" should not exist + And as "Alice" folder "/folderA/folderB" should exist + And as "Brian" folder "/Shares/folderB" should exist + And as "Alice" file "/folderA/folderB/fileB.txt" should exist + And as "Brian" file "/Shares/folderA/folderB/fileB.txt" should exist + And as "Brian" file "/Shares/folderB/fileB.txt" should exist + + @notToImplementOnOCIS + Scenario: share receiver moves a whole share inside a local folder + Given user "Brian" has created folder "localFolder" + And user "Brian" has uploaded file with content "local text" to "/localFolder/localFile.txt" + When user "Brian" moves folder "Shares/folderB" to "localFolder/folderB" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/localFolder/folderB" should exist + And as "Brian" file "/localFolder/folderB/fileB.txt" should exist + And as "Brian" file "/localFolder/localFile.txt" should exist + But as "Brian" file "/Shares/folderB/fileB.txt" should not exist + + @notToImplementOnOCIS + Scenario: share receiver moves a whole share inside a local folder then moves the local folder inside a received share + Given user "Brian" has created folder "localFolder" + And user "Brian" has uploaded file with content "local text" to "/localFolder/localFile.txt" + When user "Brian" moves folder "Shares/folderB" to "localFolder/folderB" using the WebDAV API + And user "Brian" moves folder "localFolder" to "Shares/folderA/localFolder" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" folder "/folderA/localFolder" should exist + And as "Brian" folder "/Shares/folderA/localFolder" should exist + And as "Alice" file "/folderA/localFolder/localFile.txt" should exist + And as "Brian" file "/Shares/folderA/localFolder/localFile.txt" should exist + # folderB now exists separately, and is no longer inside localFolder + And as "Alice" file "/folderB/fileB.txt" should exist + And as "Brian" file "/Shares/folderB/fileB.txt" should exist + But as "Alice" folder "/folderA/localFolder/folderB" should not exist + And as "Brian" folder "/Shares/folderA/localFolder/folderB" should not exist + + @notToImplementOnOCIS + Scenario: share receiver moves a whole share inside a local folder then tries to move the share inside a received share + Given user "Brian" has created folder "localFolder" + And user "Brian" has uploaded file with content "local text" to "/localFolder/localFile.txt" + When user "Brian" moves folder "Shares/folderB" to "localFolder/folderB" using the WebDAV API + And user "Brian" moves folder "localFolder/folderB" to "Shares/folderA/folderB" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 403" respectively + And as "Alice" file "/folderB/fileB.txt" should exist + And as "Brian" folder "/localFolder/folderB" should exist + And as "Brian" file "/localFolder/folderB/fileB.txt" should exist + But as "Alice" folder "/folderA/folderB" should not exist + And as "Brian" folder "/Shares/folderA/folderB" should not exist + + + Scenario: share receiver moves a local folder inside a received share (local folder does not have a share in it) + Given user "Brian" has created folder "localFolder" + And user "Brian" has created folder "localFolder/subFolder" + And user "Brian" has uploaded file with content "local text" to "/localFolder/localFile.txt" + When user "Brian" moves folder "localFolder" to "Shares/folderA/localFolder" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "/folderA/localFolder" should exist + And as "Brian" folder "/Shares/folderA/localFolder" should exist + And as "Alice" folder "/folderA/localFolder/subFolder" should exist + And as "Brian" folder "/Shares/folderA/localFolder/subFolder" should exist + And as "Alice" file "/folderA/localFolder/localFile.txt" should exist + And as "Brian" file "/Shares/folderA/localFolder/localFile.txt" should exist + + @skipOnOcV10 + Scenario: share receiver tries to move a whole share inside a local folder + Given user "Brian" has created folder "localFolder" + And user "Brian" has uploaded file with content "local text" to "/localFolder/localFile.txt" + # On oCIS you cannot move received shares out of the "Shares" folder + When user "Brian" moves folder "Shares/folderB" to "localFolder/folderB" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/folderB/fileB.txt" should exist + And as "Brian" file "/Shares/folderB/fileB.txt" should exist diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/accessToShare.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/accessToShare.feature new file mode 100644 index 00000000000..9c3c3163c35 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/accessToShare.feature @@ -0,0 +1,75 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + + @smokeTest + Scenario Outline: Sharee can see the share + Given using OCS API version "" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" gets all the shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share_id should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: Sharee can see the filtered share + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile1.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" + When user "Brian" gets all the shares shared with him that are received as file "/Shares/textfile1.txt" using the provisioning API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share_id should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @issue-ocis-reva-260 + Scenario Outline: Sharee can't see the share that is filtered out + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/textfile1.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile1.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" + When user "Brian" gets all the shares shared with him that are received as file "/Shares/textfile0.txt" using the provisioning API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share id should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @issue-ocis-reva-34 @issue-ocis-reva-194 + Scenario Outline: Sharee can see the group share + Given using OCS API version "" + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" gets all the shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share_id should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/accessToSharesFolder.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/accessToSharesFolder.feature new file mode 100644 index 00000000000..a6f4da75122 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/accessToSharesFolder.feature @@ -0,0 +1,119 @@ +@api @files_sharing-app-required @notToImplementOnOCIS +Feature: write directly into the folder for received shares + On ownCloud10 with the folder for received shares set, for example, to "Shares" + A user should see a "Shares" folder when they have received a share. + A user should be able to add their own resources into the "Shares" folder + and those resources will act like any resource that is local to the user. + Even if the user has no more received shares, the "Shares" folder is still there. + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario: the Shares folder does not exist before the first share is received + Then as "Alice" folder "/Shares" should not exist + And as "Brian" folder "/Shares" should not exist + + + Scenario: the Shares folder does not exist if no share has been accepted + Given user "Alice" has created folder "/shared" + When user "Alice" shares folder "/shared" with user "Brian" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And as "Brian" folder "/Shares" should not exist + And as "Alice" folder "/Shares" should not exist + + + Scenario: the Shares folder exists for the sharee after a share is accepted + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + When user "Brian" accepts share "/shared" offered by user "Alice" using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And as "Brian" folder "/Shares" should exist + But as "Alice" folder "/Shares" should not exist + + + Scenario: the Shares folder exists after a share is received, accepted and deleted + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Alice" deletes the last share using the sharing API + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And as "Brian" folder "/Shares" should exist + But as "Alice" folder "/Shares" should not exist + + + Scenario: the Shares folder can be created first by the user + Given user "Alice" has created folder "/shared" + When user "Brian" creates folder "/Shares" using the WebDAV API + And user "Brian" creates folder "/Shares/aFolder" using the WebDAV API + And user "Alice" shares folder "/shared" with user "Brian" using the sharing API + And user "Brian" accepts share "/shared" offered by user "Alice" using the sharing API + # OCS status code is available only for the Sharing API request + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on each endpoint should be "201, 201, 200, 200" respectively + And as "Brian" folder "/Shares" should exist + And as "Brian" folder "/Shares/shared" should exist + + + Scenario: the user can have created a matching resource in Shares before receiving a share + Given user "Alice" has created folder "/shared" + And user "Alice" has uploaded file with content "shared data" to "/shared/aFile.txt" + And user "Brian" has created folder "/Shares" + And user "Brian" has created folder "/Shares/shared" + And user "Brian" has uploaded file with content "data of Brian" to "/Shares/shared/aFile.txt" + When user "Alice" shares folder "/shared" with user "Brian" using the sharing API + And user "Brian" accepts share "/shared" offered by user "Alice" using the sharing API + Then the OCS status code of responses on all endpoints should be "100" + And the HTTP status code of responses on all endpoints should be "200" + And as "Brian" folder "/Shares" should exist + And as "Brian" folder "/Shares/shared" should exist + And as "Brian" folder "/Shares/shared (2)" should exist + And the content of file "/Shares/shared/aFile.txt" for user "Brian" should be "data of Brian" + And the content of file "/Shares/shared (2)/aFile.txt" for user "Brian" should be "shared data" + + + Scenario: the user can write directly into the Shares folder + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + When user "Brian" uploads file with content "some data" to "/Shares/textfile.txt" using the WebDAV API + And user "Brian" creates folder "/Shares/aFolder" using the WebDAV API + And user "Brian" uploads file with content "more data" to "/Shares/aFolder/aFile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + And as "Brian" file "/Shares/textfile.txt" should exist + And the content of file "/Shares/textfile.txt" for user "Brian" should be "some data" + And as "Brian" file "/Shares/aFolder/aFile.txt" should exist + And the content of file "Shares/aFolder/aFile.txt" for user "Brian" should be "more data" + + + Scenario: the user can move files directly into the Shares folder + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has uploaded file with content "some data" to "/textfile.txt" + When user "Brian" moves file "/textfile.txt" to "/Shares/textfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/textfile.txt" should exist + And the content of file "Shares/textfile.txt" for user "Brian" should be "some data" + + + Scenario: the user can delete files that they wrote into the Shares folder + Given user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has uploaded file with content "some data" to "/Shares/textfile.txt" + And user "Brian" has created folder "/Shares/aFolder" + When user "Brian" deletes file "/Shares/textfile.txt" using the WebDAV API + And user "Brian" deletes folder "/Shares/aFolder" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Brian" folder "/Shares" should exist + But as "Brian" file "/Shares/textfile.txt" should not exist + And as "Brian" folder "/Shares/aFolder" should not exist diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature new file mode 100644 index 00000000000..032f308d657 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature @@ -0,0 +1,149 @@ +@api @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1328 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @smokeTest + Scenario Outline: moving a file into a share as recipient + Given using DAV path + And user "Alice" has created folder "/shared" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has uploaded file with content "some data" to "/textfile0.txt" + When user "Brian" moves file "textfile0.txt" to "/Shares/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/shared/shared_file.txt" should exist + And as "Alice" file "/shared/shared_file.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @smokeTest @files_trashbin-app-required @notToImplementOnOCIS + Scenario Outline: moving a file out of a share as recipient creates a backup for the owner + Given using DAV path + And user "Alice" has created folder "/shared" + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared file "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has moved folder "/Shares/shared" to "/shared_renamed" + When user "Brian" moves file "/shared_renamed/shared_file.txt" to "/taken_out.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/taken_out.txt" should exist + And as "Alice" file "/shared/shared_file.txt" should not exist + And as "Alice" file "/shared_file.txt" should exist in the trashbin + Examples: + | dav_version | + | old | + | new | + + @files_trashbin-app-required @notToImplementOnOCIS + Scenario Outline: moving a folder out of a share as recipient creates a backup for the owner + Given using DAV path + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared file "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has moved folder "/Shares/shared" to "/shared_renamed" + When user "Brian" moves folder "/shared_renamed/sub" to "/taken_out" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/taken_out" should exist + And as "Alice" folder "/shared/sub" should not exist + And as "Alice" folder "/sub" should exist in the trashbin + And as "Alice" file "/sub/shared_file.txt" should exist in the trashbin + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Move files between shares by same user + Given using DAV path + And user "Alice" has created folder "share1" + And user "Alice" has created folder "share2" + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has moved file "textfile0.txt" to "share1/textfile0.txt" + And user "Alice" has shared folder "/share1" with user "Brian" + And user "Alice" has shared folder "/share2" with user "Brian" + And user "Brian" has accepted share "/share1" offered by user "Alice" + And user "Brian" has accepted share "/share2" offered by user "Alice" + When user "Brian" moves file "/Shares/share1/textfile0.txt" to "/Shares/share2/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/share1/textfile0.txt" should not exist + But as "Brian" file "/Shares/share2/textfile0.txt" should exist + And as "Alice" file "share1/textfile0.txt" should not exist + But as "Alice" file "share2/textfile0.txt" should exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Move files between shares by same user added by sharee + Given using DAV path + And user "Alice" has created folder "share1" + And user "Alice" has created folder "share2" + And user "Brian" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has shared folder "/share1" with user "Brian" + And user "Alice" has shared folder "/share2" with user "Brian" + And user "Brian" has accepted share "/share1" offered by user "Alice" + And user "Brian" has accepted share "/share2" offered by user "Alice" + When user "Brian" moves file "textfile0.txt" to "/Shares/share1/shared_file.txt" using the WebDAV API + And user "Brian" moves file "/Shares/share1/shared_file.txt" to "/Shares/share2/shared_file.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + And as "Brian" file "/Shares/share1/shared_file.txt" should not exist + And as "Alice" file "share1/shared_file.txt" should not exist + But as "Brian" file "/Shares/share2/shared_file.txt" should exist + And as "Alice" file "share2/shared_file.txt" should exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Move files between shares by different users + Given using DAV path + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has created folder "/PARENT" + And user "Brian" has created folder "/PARENT" + And user "Alice" has moved file "textfile0.txt" to "PARENT/shared_file.txt" + And user "Alice" has shared folder "/PARENT" with user "Carol" + And user "Brian" has shared folder "/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Alice" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + When user "Carol" moves file "/Shares/PARENT/shared_file.txt" to "/Shares/PARENT (2)/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "/Shares/PARENT (2)/shared_file.txt" should exist + And as "Brian" file "PARENT/shared_file.txt" should exist + But as "Alice" file "PARENT/shared_file.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: overwrite a received file share + Given using DAV path + And user "Alice" has uploaded file with content "this is the old content" to "/textfile1.txt" + And user "Alice" has shared file "/textfile1.txt" with user "Brian" + And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" + When user "Brian" uploads file with content "this is a new content" to "/Shares/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" file "Shares/textfile1.txt" should exist + And the content of file "Shares/textfile1.txt" for user "Brian" should be "this is a new content" + And the content of file "textfile1.txt" for user "Alice" should be "this is a new content" + Examples: + | dav_version | + | old | + | new | + diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature new file mode 100644 index 00000000000..0018e5b2776 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature @@ -0,0 +1,222 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @smokeTest @issue-ocis-reva-262 + Scenario Outline: getting all shares of a user using that user + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/file_to_share.txt" + And user "Alice" has shared file "file_to_share.txt" with user "Brian" + And user "Brian" has accepted share "/file_to_share.txt" offered by user "Alice" + When user "Alice" gets all shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And file "/Shares/file_to_share.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-65 + Scenario Outline: getting all shares of a user using another user + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When the administrator gets all shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And file "/Shares/textfile0.txt" should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: getting all shares of a file + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Carol | + | David | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile0.txt" with user "Carol" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" gets all the shares from the file "textfile0.txt" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be included in the response + And user "Carol" should be included in the response + And user "David" should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: getting all shares of a file with reshares + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Carol | + | David | + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has shared file "/Shares/textfile0.txt" with user "Carol" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Brian" + When user "Alice" gets all the shares with reshares from the file "textfile0.txt" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be included in the response + And user "Carol" should be included in the response + And user "David" should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest + Scenario Outline: User's own shares reshared to him don't appear when getting "shared with me" shares + Given using OCS API version "" + And group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Carol" has been added to group "grp1" + And user "Carol" has created folder "/shared" + And user "Carol" has uploaded file with content "some data" to "/shared/shared_file.txt" + And user "Carol" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Carol" + And user "Brian" has shared folder "/Shares/shared" with group "grp1" + # no need to accept this share as it is Carol's file + When user "Carol" gets all the shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the last share id should not be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @toFixOnOCIS @issue-ocis-reva-357 @issue-ocis-reva-301 @issue-ocis-reva-302 + #after fixing all the issues merge this scenario with the one below + Scenario Outline: getting share info of a share + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/file_to_share.txt" + And user "Alice" has shared file "file_to_share.txt" with user "Brian" + And user "Brian" has accepted share "/file_to_share.txt" offered by user "Alice" + When user "Alice" gets the info of the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | id | A_STRING | + | item_type | file | + | item_source | A_STRING | + | share_type | user | + | share_with | %username% | + | file_source | A_STRING | + | file_target | /Shares/file_to_share.txt | + | path | /file_to_share.txt | + | permissions | share,read,update | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | share_with_displayname | %displayname% | + | displayname_owner | %displayname% | + | mimetype | text/plain | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @toFixOnOCIS @issue-ocis-reva-357 @issue-ocis-reva-301 @issue-ocis-reva-302 + #after fixing all the issues merge this scenario with the one above + Scenario Outline: getting share info of a share (Bug demonstration for ocis) + Given using OCS API version "" + And user "Alice" has uploaded file with content "some data" to "/file_to_share.txt" + And user "Alice" has shared file "file_to_share.txt" with user "Brian" + And user "Brian" has accepted share "/file_to_share.txt" offered by user "Alice" + When user "Alice" gets the info of the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | id | A_STRING | + | item_type | file | + | item_source | A_STRING | + | share_type | user | + | share_with | %username% | + | file_source | A_STRING | + | file_target | /Shares/file_to_share.txt | + | path | /file_to_share.txt | + | permissions | share,read,update | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | +# | share_with_displayname | %displayname% | +# | displayname_owner | %displayname% | +# | mimetype | text/plain | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-374 + Scenario Outline: Get a share with a user that didn't receive the share + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Carol" gets the info of the last share using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + @skipOnLDAP @issue-ocis-reva-194 @skipOnGraph + Scenario: Share of folder to a group, remove user from that group + Given using OCS API version "1" + And user "Carol" has been created with default attributes and without skeleton files + And group "group0" has been created + And user "Brian" has been added to group "group0" + And user "Carol" has been added to group "group0" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + And user "Alice" has shared folder "/PARENT" with group "group0" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Carol" has accepted share "/PARENT" offered by user "Alice" + When the administrator removes user "Carol" from group "group0" using the provisioning API + Then the HTTP status code should be "200" + And user "Brian" should see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + But user "Carol" should not see the following elements + | /Shares/PARENT/ | + | /Shares/PARENT/parent.txt | + + @issue-ocis-reva-372 + Scenario Outline: getting all the shares inside the folder + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "some data" to "/PARENT/parent.txt" + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Alice" gets all the shares inside the folder "PARENT" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And file "/Shares/parent.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | pending_share_path | + | 1 | 100 | /parent.txt | + | 2 | 200 | /parent.txt | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesPendingFiltered.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesPendingFiltered.feature new file mode 100644 index 00000000000..3181330c3f8 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesPendingFiltered.feature @@ -0,0 +1,59 @@ +@api @files_sharing-app-required +Feature: get the pending shares filtered by type (user, group etc) + As a user + I want to be able to know the pending shares that I have received of a particular type (user, group etc) + So that I can reduce the amount of data that has to be transferred to be just the data that I need + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/folderToShareWithUser" + And user "Alice" has created folder "/folderToShareWithGroup" + And user "Alice" has created folder "/folderToShareWithPublic" + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShareWithUser.txt" + And user "Alice" has uploaded file with content "file to share with group" to "/fileToShareWithGroup.txt" + And user "Alice" has uploaded file with content "file to share with public" to "/fileToShareWithPublic.txt" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + + + Scenario Outline: getting pending shares received from users + Given using OCS API version "" + When user "Brian" gets the pending user shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 1 file or folder should be included in the response + And folder "/Shares/folderToShareWithUser" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting pending shares received from groups + Given using OCS API version "" + When user "Brian" gets the pending group shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 1 file or folder should be included in the response + And folder "/Shares/folderToShareWithGroup" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFiltered.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFiltered.feature new file mode 100644 index 00000000000..ed35bfb1de7 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFiltered.feature @@ -0,0 +1,63 @@ +@api @files_sharing-app-required +Feature: get the received shares filtered by type (user, group etc) + As a user + I want to be able to know the shares that I have received of a particular type (user, group etc) + So that I can reduce the amount of data that has to be transferred to be just the data that I need + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/folderToShareWithUser" + And user "Alice" has created folder "/folderToShareWithGroup" + And user "Alice" has created folder "/folderToShareWithPublic" + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShareWithUser.txt" + And user "Alice" has uploaded file with content "file to share with group" to "/fileToShareWithGroup.txt" + And user "Alice" has uploaded file with content "file to share with public" to "/fileToShareWithPublic.txt" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + + + Scenario Outline: getting shares received from users + Given using OCS API version "" + When user "Brian" gets the user shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 2 files or folders should be included in the response + And folder "/Shares/folderToShareWithUser" should be included in the response + And file "/Shares/fileToShareWithUser.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares received from groups + Given using OCS API version "" + When user "Brian" gets the group shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 2 files or folders should be included in the response + And folder "/Shares/folderToShareWithGroup" should be included in the response + And folder "/Shares/fileToShareWithGroup.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature new file mode 100644 index 00000000000..e1cae806dc5 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesReceivedFilteredEmpty.feature @@ -0,0 +1,94 @@ +@api @files_sharing-app-required +Feature: get the received shares filtered by type (user, group etc) + As a user + I want to be able to know the shares that I have received of a particular type (user, group etc) + So that I can reduce the amount of data that has to be transferred to be just the data that I need + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/folderToShareWithUser" + And user "Alice" has created folder "/folderToShareWithGroup" + And user "Alice" has created folder "/folderToShareWithPublic" + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShareWithUser.txt" + And user "Alice" has uploaded file with content "file to share with group" to "/fileToShareWithGroup.txt" + And user "Alice" has uploaded file with content "file to share with public" to "/fileToShareWithPublic.txt" + + + Scenario Outline: getting shares received from users when there are none + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + When user "Brian" gets the user shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares received from groups when there are none + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + When user "Brian" gets the group shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares received from public links when there are none + # Note: public links are purposely created in this scenario + # users do not receive public links, so asking for a list of public links + # that are "shared with me" should always return an empty list. + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + When user "Brian" gets the public link shares shared with him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFiltered.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFiltered.feature new file mode 100644 index 00000000000..9cc3c336cda --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFiltered.feature @@ -0,0 +1,93 @@ +@api @files_sharing-app-required +Feature: get shares filtered by type (user, group etc) + As a user + I want to be able to know the shares that I have made of a particular type (user, group etc) + So that I can reduce the amount of data that has to be transferred to be just the data that I need + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/folderToShareWithUser" + And user "Alice" has created folder "/folderToShareWithGroup" + And user "Alice" has created folder "/folderToShareWithPublic" + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShareWithUser.txt" + And user "Alice" has uploaded file with content "file to share with group" to "/fileToShareWithGroup.txt" + And user "Alice" has uploaded file with content "file to share with public" to "/fileToShareWithPublic.txt" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + + + Scenario Outline: getting shares shared to users + Given using OCS API version "" + When user "Alice" gets the user shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 2 files or folders should be included in the response + And folder "/Shares/folderToShareWithUser" should be included in the response + And file "/Shares/fileToShareWithUser.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares shared to groups + Given using OCS API version "" + When user "Alice" gets the group shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 2 files or folders should be included in the response + And folder "/Shares/folderToShareWithGroup" should be included in the response + And folder "/Shares/fileToShareWithGroup.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares shared to public links + Given using OCS API version "" + When user "Alice" gets the public link shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 2 files or folders should be included in the response + And folder "/folderToShareWithPublic" should be included in the response + And folder "/fileToShareWithPublic.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares shared to users and groups + Given using OCS API version "" + When user "Alice" gets the user and group shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And exactly 4 files or folders should be included in the response + And folder "/Shares/folderToShareWithUser" should be included in the response + And file "/Shares/fileToShareWithUser.txt" should be included in the response + And folder "/Shares/folderToShareWithGroup" should be included in the response + And folder "/Shares/fileToShareWithGroup.txt" should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature new file mode 100644 index 00000000000..9baa881e47f --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares1/gettingSharesSharedFilteredEmpty.feature @@ -0,0 +1,83 @@ +@api @files_sharing-app-required +Feature: get shares filtered by type (user, group etc) + As a user + I want to be able to know the shares that I have made of a particular type (user, group etc) + So that I can reduce the amount of data that has to be transferred to be just the data that I need + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/folderToShareWithUser" + And user "Alice" has created folder "/folderToShareWithGroup" + And user "Alice" has created folder "/folderToShareWithPublic" + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShareWithUser.txt" + And user "Alice" has uploaded file with content "file to share with group" to "/fileToShareWithGroup.txt" + And user "Alice" has uploaded file with content "file to share with public" to "/fileToShareWithPublic.txt" + + + Scenario Outline: getting shares shared to users when there are none + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + When user "Alice" gets the user shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares shared to groups when there are none + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /folderToShareWithPublic | + | permissions | read | + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has created a public link share with settings + | path | /fileToShareWithPublic.txt | + | permissions | read | + When user "Alice" gets the group shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: getting shares shared to public links when there are none + Given using OCS API version "" + And user "Alice" has shared folder "/folderToShareWithUser" with user "Brian" + And user "Brian" has accepted share "/folderToShareWithUser" offered by user "Alice" + And user "Alice" has shared folder "/folderToShareWithGroup" with group "grp1" + And user "Brian" has accepted share "/folderToShareWithGroup" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithUser.txt" with user "Brian" + And user "Brian" has accepted share "/fileToShareWithUser.txt" offered by user "Alice" + And user "Alice" has shared file "/fileToShareWithGroup.txt" with group "grp1" + And user "Brian" has accepted share "/fileToShareWithGroup.txt" offered by user "Alice" + When user "Alice" gets the public link shares shared by him using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And no files or folders should be included in the response + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares2/getWebDAVSharePermissions.feature b/tests/acceptance/features/coreApiShareOperationsToShares2/getWebDAVSharePermissions.feature new file mode 100644 index 00000000000..559f62dca8e --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares2/getWebDAVSharePermissions.feature @@ -0,0 +1,346 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @smokeTest + Scenario Outline: Correct webdav share-permissions for owned file + Given using DAV path + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + When user "Alice" gets the following properties of file "/tmp.txt" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "201" + And the single response should contain a property "ocs:share-permissions" with value "19" + Examples: + | dav-path | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: Correct webdav share-permissions for received file with edit and reshare permissions + Given using DAV path + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has shared file "/tmp.txt" with user "Brian" + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Brian" gets the following properties of file "/Shares/tmp.txt" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "19" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared file with edit and reshare permissions + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has created a share with settings + | path | /tmp.txt | + | shareType | group | + | permissions | share,update,read | + | shareWith | grp1 | + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Brian" gets the following properties of file "/Shares/tmp.txt" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "19" + Examples: + | dav-path | + | old | + | new | + + @issue-ocis-2213 + Scenario Outline: Correct webdav share-permissions for received file with edit permissions but no reshare permissions + Given using DAV path + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has shared file "tmp.txt" with user "Brian" + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | update,read | + Then the HTTP status code should be "200" + And as user "Brian" file "/Shares/tmp.txt" should contain a property "ocs:share-permissions" with value "3" + Examples: + | dav-path | + | old | + | new | + + @issue-ocis-2213 + Scenario Outline: Correct webdav share-permissions for received group shared file with edit permissions but no reshare permissions + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has created a share with settings + | path | /tmp.txt | + | shareType | group | + | permissions | update,read | + | shareWith | grp1 | + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Brian" gets the following properties of file "/Shares/tmp.txt" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "3" + Examples: + | dav-path | + | old | + | new | + + @issue-ocis-2213 + Scenario Outline: Correct webdav share-permissions for received file with reshare permissions but no edit permissions + Given using DAV path + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has shared file "tmp.txt" with user "Brian" + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | share,read | + Then the HTTP status code should be "200" + And as user "Brian" file "/Shares/tmp.txt" should contain a property "ocs:share-permissions" with value "17" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared file with reshare permissions but no edit permissions + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "foo" to "/tmp.txt" + And user "Alice" has created a share with settings + | path | /tmp.txt | + | shareType | group | + | permissions | share,read | + | shareWith | grp1 | + And user "Brian" has accepted share "/tmp.txt" offered by user "Alice" + When user "Brian" gets the following properties of file "/Shares/tmp.txt" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "17" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for owned folder + Given using DAV path + And user "Alice" has created folder "/tmp" + When user "Alice" gets the following properties of folder "/" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "201" + And the single response should contain a property "ocs:share-permissions" with value "31" + Examples: + | dav-path | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: Correct webdav share-permissions for received folder with all permissions + Given using DAV path + And user "Alice" has created folder "/tmp" + And user "Alice" has shared file "/tmp" with user "Brian" + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "31" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared folder with all permissions + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/tmp" + And user "Alice" has created a share with settings + | path | tmp | + | shareType | group | + | shareWith | grp1 | + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "31" + Examples: + | dav-path | + | old | + | new | + + @issue-ocis-2213 + Scenario Outline: Correct webdav share-permissions for received folder with all permissions but edit + Given using DAV path + And user "Alice" has created folder "/tmp" + And user "Alice" has shared file "/tmp" with user "Brian" + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | share,delete,create,read | + Then the HTTP status code should be "200" + And as user "Brian" folder "/Shares/tmp" should contain a property "ocs:share-permissions" with value "29" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared folder with all permissions but edit + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/tmp" + And user "Alice" has created a share with settings + | path | tmp | + | shareType | group | + | shareWith | grp1 | + | permissions | share,delete,create,read | + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "29" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received folder with all permissions but create + Given using DAV path + And user "Alice" has created folder "/tmp" + And user "Alice" has shared file "/tmp" with user "Brian" + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | share,delete,update,read | + Then the HTTP status code should be "200" + And as user "Brian" folder "/Shares/tmp" should contain a property "ocs:share-permissions" with value "27" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared folder with all permissions but create + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/tmp" + And user "Alice" has created a share with settings + | path | tmp | + | shareType | group | + | shareWith | grp1 | + | permissions | share,delete,update,read | + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "27" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received folder with all permissions but delete + Given using DAV path + And user "Alice" has created folder "/tmp" + And user "Alice" has shared file "/tmp" with user "Brian" + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the HTTP status code should be "200" + And as user "Brian" folder "/Shares/tmp" should contain a property "ocs:share-permissions" with value "23" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared folder with all permissions but delete + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/tmp" + And user "Alice" has created a share with settings + | path | tmp | + | shareType | group | + | shareWith | grp1 | + | permissions | share,create,update,read | + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "23" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received folder with all permissions but share + Given using DAV path + And user "Alice" has created folder "/tmp" + And user "Alice" has shared file "/tmp" with user "Brian" + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | change | + Then the HTTP status code should be "200" + And as user "Brian" folder "/Shares/tmp" should contain a property "ocs:share-permissions" with value "15" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Correct webdav share-permissions for received group shared folder with all permissions but share + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/tmp" + And user "Alice" has created a share with settings + | path | tmp | + | shareType | group | + | shareWith | grp1 | + | permissions | change | + And user "Brian" has accepted share "/tmp" offered by user "Alice" + When user "Brian" gets the following properties of folder "/Shares/tmp" using the WebDAV API + | propertyName | + | ocs:share-permissions | + Then the HTTP status code should be "200" + And the single response should contain a property "ocs:share-permissions" with value "15" + Examples: + | dav-path | + | old | + | new | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature b/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature new file mode 100644 index 00000000000..a29c1f9dfc5 --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature @@ -0,0 +1,156 @@ +@api @files_sharing-app-required +Feature: share access by ID + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: Get a share with a valid share ID + Given using OCS API version "" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/textfile0.txt" offered by user "Alice" using the sharing API + And user "Alice" gets share with id "%last_share_id%" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | share_with | %username% | + | share_with_displayname | %displayname% | + | file_target | /Shares/textfile0.txt | + | path | /textfile0.txt | + | permissions | share,read,update | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | item_type | file | + | mimetype | text/plain | + | storage_id | ANY_VALUE | + | share_type | user | + And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Get a share with an invalid share id + Given using OCS API version "" + When user "Alice" gets share with id "" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And the API should not return any data + Examples: + | ocs_api_version | share_id | http_status_code | + | 1 | 2333311 | 200 | + | 2 | 2333311 | 404 | + | 1 | helloshare | 200 | + | 2 | helloshare | 404 | + | 1 | $#@r3 | 200 | + | 2 | $#@r3 | 404 | + | 1 | 0 | 200 | + | 2 | 0 | 404 | + + + Scenario Outline: accept a share using the share Id + Given using OCS API version "" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + And user "Brian" accepts share with ID "%last_share_id%" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should see the following elements + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the accepted state + | path | + | /Shares/textfile0.txt | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: accept a share using the invalid share Id + Given parameter "shareapi_auto_accept_share" of app "core" has been set to "yes" + And using OCS API version "" + When user "Brian" accepts share with ID "" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And the API should not return any data + Examples: + | ocs_api_version | share_id | http_status_code | + | 1 | 2333311 | 200 | + | 2 | 2333311 | 404 | + | 1 | helloshare | 200 | + | 2 | helloshare | 404 | + | 1 | $#@r3 | 200 | + | 2 | $#@r3 | 404 | + | 1 | 0 | 200 | + | 2 | 0 | 404 | + + + Scenario Outline: accept a share using empty share Id + Given parameter "shareapi_auto_accept_share" of app "core" has been set to "yes" + And using OCS API version "" + When user "Brian" accepts share with ID "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And the API should not return any data + Examples: + | ocs_api_version | http_status_code | ocs_status_code | + | 1 | 200 | 999 | + | 2 | 500 | 500 | + + + Scenario Outline: decline a share using the share Id + Given using OCS API version "" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" declines share with ID "%last_share_id%" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should not see the following elements + | /Shares/textfile0.txt | + And the sharing API should report to user "Brian" that these shares are in the declined state + | path | + | | + Examples: + | ocs_api_version | ocs_status_code | declined_share_path | + | 1 | 100 | /Shares/textfile0.txt | + | 2 | 200 | /Shares/textfile0.txt | + + + Scenario Outline: decline a share using a invalid share Id + Given parameter "shareapi_auto_accept_share" of app "core" has been set to "yes" + And using OCS API version "" + When user "Brian" declines share with ID "" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And the API should not return any data + Examples: + | ocs_api_version | share_id | http_status_code | + | 1 | 2333311 | 200 | + | 2 | 2333311 | 404 | + | 1 | helloshare | 200 | + | 2 | helloshare | 404 | + | 1 | $#@r3 | 200 | + | 2 | $#@r3 | 404 | + | 1 | 0 | 200 | + | 2 | 0 | 404 | + + + Scenario Outline: decline a share using empty share Id + Given parameter "shareapi_auto_accept_share" of app "core" has been set to "yes" + And using OCS API version "" + When user "Brian" declines share with ID "" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And the API should not return any data + Examples: + | ocs_api_version | http_status_code | ocs_status_code | + | 1 | 200 | 999 | + | 2 | 500 | 500 | diff --git a/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature b/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature new file mode 100644 index 00000000000..4e48ddc3d5a --- /dev/null +++ b/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature @@ -0,0 +1,307 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Uploading file to a user read-only share folder does not work + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | read | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/FOLDER/textfile.txt" should not exist + + + Scenario Outline: Uploading file to a group read-only share folder does not work + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | group | + | permissions | read | + | shareWith | grp1 | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/FOLDER/textfile.txt" should not exist + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Uploading file to a user upload-only share folder works + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | create | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Brian" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Uploading file to a group upload-only share folder works + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | group | + | permissions | create | + | shareWith | grp1 | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Brian" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path | + | old | + | new | + + @smokeTest + Scenario Outline: Uploading file to a user read/write share folder works + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | change | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Uploading file to a group read/write share folder works + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path | + | old | + | new | + + @smokeTest @skipOnGraph + Scenario Outline: Check quota of owners parent directory of a shared file + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And the quota of user "Brian" has been set to "0" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/myfile.txt" + And user "Alice" has shared file "myfile.txt" with user "Brian" + And user "Brian" has accepted share "/myfile.txt" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/myfile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the following headers should match these regular expressions for user "Brian" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/myfile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav-path | + | old | + | new | + + @skipOnGraph + Scenario Outline: Uploading to a user shared folder with read/write permission when the sharer has unsufficient quota does not work + Given using DAV path + And user "Brian" has been created with default attributes and small skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | change | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And the quota of user "Alice" has been set to "0" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API + Then the HTTP status code should be "507" + And as "Alice" file "/FOLDER/myfile.txt" should not exist + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Uploading to a group shared folder with read/write permission when the sharer has unsufficient quota does not work + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And the quota of user "Alice" has been set to "0" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API + Then the HTTP status code should be "507" + And as "Alice" file "/FOLDER/myfile.txt" should not exist + Examples: + | dav-path | + | old | + | new | + + @skipOnGraph + Scenario Outline: Uploading to a user shared folder with upload-only permission when the sharer has insufficient quota does not work + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | create | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And the quota of user "Alice" has been set to "0" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API + Then the HTTP status code should be "507" + And as "Alice" file "/FOLDER/myfile.txt" should not exist + Examples: + | dav-path | + | old | + | new | + + @skipOnGraph + Scenario Outline: Uploading to a group shared folder with upload-only permission when the sharer has insufficient quota does not work + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | group | + | permissions | create | + | shareWith | grp1 | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And the quota of user "Alice" has been set to "0" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API + Then the HTTP status code should be "507" + And as "Alice" file "/FOLDER/myfile.txt" should not exist + Examples: + | dav-path | + | old | + | new | + + @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Uploading a file in to a shared folder without edit permissions + Given using new DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/READ_ONLY" + And user "Alice" has created a share with settings + | path | /READ_ONLY | + | shareType | user | + | permissions | read | + | shareWith | Brian | + And user "Brian" has accepted share "/READ_ONLY" offered by user "Alice" + When user "Brian" uploads the following chunks to "/Shares/READ_ONLY/myfile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | hallo | + | 2 | welt | + Then the HTTP status code should be "403" + And as "Alice" file "/FOLDER/myfile.txt" should not exist + + + Scenario Outline: Sharer can download file uploaded with different permission by sharee to a shared folder + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "some content" to "/Shares/FOLDER/textFile.txt" using the WebDAV API + And user "Alice" downloads file "/FOLDER/textFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded content should be "some content" + Examples: + | dav-path | permissions | + | old | change | + | new | create | + + + Scenario Outline: upload an empty file (size zero byte) to a shared folder + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/folder-to-share" + And user "Brian" has shared folder "/folder-to-share" with user "Alice" + And user "Alice" has accepted share "/folder-to-share" offered by user "Brian" + When user "Alice" uploads file "filesForUpload/zerobyte.txt" to "/Shares/folder-to-share/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/Shares/folder-to-share/zerobyte.txt" should exist + And the content of file "/Shares/folder-to-share/zerobyte.txt" for user "Alice" should be "" + Examples: + | dav_version | + | old | + | new | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature new file mode 100644 index 00000000000..5dd24d6924b --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/accessToPublicLinkShare.feature @@ -0,0 +1,59 @@ +@api @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-282 +Feature: accessing a public link share + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + + + Scenario: Access to the preview of password protected public link without providing the password is not allowed + Given the administrator has enabled DAV tech_preview + And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "testavatar.jpg" + And user "Alice" has created a public link share with settings + | path | /testavatar.jpg | + | permissions | change | + | password | testpass1 | + When the public accesses the preview of file "testavatar.jpg" from the last shared public link using the sharing API + Then the HTTP status code should be "404" + + + Scenario: Access to the preview of public shared file without password + Given the administrator has enabled DAV tech_preview + And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "testavatar.jpg" + And user "Alice" has created a public link share with settings + | path | /testavatar.jpg | + | permissions | change | + When the public accesses the preview of file "testavatar.jpg" from the last shared public link using the sharing API + Then the HTTP status code should be "200" + + + Scenario: Access to the preview of password protected public shared file inside a folder without providing the password is not allowed + Given the administrator has enabled DAV tech_preview + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "FOLDER/testavatar.jpg" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "FOLDER/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | /FOLDER | + | permissions | change | + | password | testpass1 | + When the public accesses the preview of the following files from the last shared public link using the sharing API + | path | + | testavatar.jpg | + | textfile0.txt | + Then the HTTP status code of responses on all endpoints should be "404" + + + Scenario: Access to the preview of public shared file inside a folder without password + Given the administrator has enabled DAV tech_preview + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "FOLDER/testavatar.jpg" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "FOLDER/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | /FOLDER | + | permissions | change | + When the public accesses the preview of the following files from the last shared public link using the sharing API + | path | + | testavatar.jpg | + | textfile0.txt | + Then the HTTP status code of responses on all endpoints should be "200" diff --git a/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature new file mode 100644 index 00000000000..d041b4f127f --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature @@ -0,0 +1,270 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-315 @issue-ocis-reva-316 + +Feature: changing a public link share + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: Public can or can-not delete file through publicly shared link depending on having delete permissions using the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | | + When the public deletes file "parent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code should be "" + And as "Alice" file "PARENT/parent.txt" exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | permissions | http-status-code | should-or-not | public-webdav-api-version | + | read,update,create | 403 | should | old | + | read,update,create,delete | 204 | should not | old | + + @issue-ocis-reva-292 + Examples: + | permissions | http-status-code | should-or-not | public-webdav-api-version | + | read,update,create | 403 | should | new | + | read,update,create,delete | 204 | should not | new | + + + Scenario Outline: Public link share permissions work correctly for renaming and share permissions read,update,create with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create | + When the public renames file "parent.txt" to "newparent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/PARENT/parent.txt" should exist + And as "Alice" file "/PARENT/newparent.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | + + @skipOnRansomwareProtection @issue-ransomware-208 + Scenario Outline: Public link share permissions work correctly for renaming and share permissions read,update,create,delete using the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public renames file "parent.txt" to "newparent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/parent.txt" should not exist + And as "Alice" file "/PARENT/newparent.txt" should exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public link share permissions work correctly for upload with share permissions read,update,create with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create | + When the public uploads file "lorem.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/PARENT/lorem.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public link share permissions work correctly for upload with share permissions read,update,create,delete with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public uploads file "lorem.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "PARENT/lorem.txt" for user "Alice" should be "test" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public cannot delete file through publicly shared link with password using an invalid password with public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public deletes file "parent.txt" from the last public link share using the password "invalid" and public WebDAV API + Then the HTTP status code should be "401" + And as "Alice" file "PARENT/parent.txt" should exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public can delete file through publicly shared link with password using the valid password with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public deletes file "parent.txt" from the last public link share using the password "newpasswd" and public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "PARENT/parent.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public tries to rename a file in a password protected share using an invalid password with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public renames file "parent.txt" to "newparent.txt" from the last public link share using the password "invalid" and public WebDAV API + Then the HTTP status code should be "401" + And as "Alice" file "/PARENT/newparent.txt" should not exist + And as "Alice" file "/PARENT/parent.txt" should exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + @skipOnRansomwareProtection @issue-ransomware-208 + Scenario Outline: Public tries to rename a file in a password protected share using the valid password with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public renames file "parent.txt" to "newparent.txt" from the last public link share using the password "newpasswd" and public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/newparent.txt" should exist + And as "Alice" file "/PARENT/parent.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public tries to upload to a password protected public share using an invalid password with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public uploads file "lorem.txt" with password "invalid" and content "test" using the public WebDAV API + Then the HTTP status code should be "401" + And as "Alice" file "/PARENT/lorem.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public tries to upload to a password protected public share using the valid password with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | change | + | password | newpasswd | + When the public uploads file "lorem.txt" with password "newpasswd" and content "test" using the public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/lorem.txt" should exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public cannot rename a file in uploadwriteonly public link share with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | uploadwriteonly | + When the public renames file "parent.txt" to "newparent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/PARENT/parent.txt" should exist + And as "Alice" file "/PARENT/newparent.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Public cannot delete a file in uploadwriteonly public link share with the public WebDAV API + Given user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | uploadwriteonly | + When the public deletes file "parent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "PARENT/parent.txt" should exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | diff --git a/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature new file mode 100644 index 00000000000..24667dd5347 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature @@ -0,0 +1,641 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-315 @issue-ocis-reva-316 + +Feature: create a public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: A new public link share of a file using the default permissions only grants read access using the public WebDAV API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download the last publicly shared file using the old public WebDAV API without a password and the content should be "Random data" + And the public should be able to download the last publicly shared file using the new public WebDAV API without a password and the content should be "Random data" + And the public upload to the last publicly shared file using the old public WebDAV API should fail with HTTP status code "403" + And the public upload to the last publicly shared file using the new public WebDAV API should fail with HTTP status code "403" + + @issue-ocis-reva-12 @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @notToImplementOnOCIS @issue-ocis-2079 + Scenario Outline: Creating a new public link share of a file with password using the old public WebDAV API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + | password | %public% | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download the last publicly shared file using the old public WebDAV API with password "%public%" and the content should be "Random data" + And the HTTP status code should be "200" + And the public download of the last publicly shared file using the old public WebDAV API with password "%regular%" should fail with HTTP status code "401" + And the value of the item "//s:message" in the response should be "Cannot authenticate over ajax calls" + And the public download of the last publicly shared file using the old public WebDAV API without a password should fail with HTTP status code "401" + And the value of the item "//s:message" in the response should be "Cannot authenticate over ajax calls" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @issue-ocis-reva-199 + Scenario Outline: Creating a new public link share of a file with password using the new public WebDAV API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + | password | %public% | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download the last publicly shared file using the new public WebDAV API with password "%public%" and the content should be "Random data" + And the HTTP status code should be "200" + And the public download of the last publicly shared file using the new public WebDAV API with password "%regular%" should fail with HTTP status code "401" + And the value of the item "//s:message" in the response should match "/Username or password was incorrect/" + And the public download of the last publicly shared file using the new public WebDAV API without a password should fail with HTTP status code "401" + And the value of the item "//s:message" in the response should match "/No 'Authorization: Basic' header found/" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Create a new public link share of a file with edit permissions + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + | permissions | read,update,create,delete | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | permissions | read,update | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download the last publicly shared file using the old public WebDAV API without a password and the content should be "Random data" + And the public should be able to download the last publicly shared file using the new public WebDAV API without a password and the content should be "Random data" + And uploading content to a public link shared file should work using the old public WebDAV API + And uploading content to a public link shared file should work using the new public WebDAV API + + @issue-ocis-2079 @issue-ocis-reva-292 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a new public link share of a folder using the default permissions only grants read access and can be accessed with no password or any password using the public WebDAV API + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "Random data" to "/PARENT/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | PARENT | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | folder | + | mimetype | httpd/unix-directory | + | file_target | /PARENT | + | path | /PARENT | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the old public WebDAV API without password and the content should be "Random data" + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "Random data" + And the public upload to the last publicly shared folder using the old public WebDAV API should fail with HTTP status code "403" + And the public upload to the last publicly shared folder using the new public WebDAV API should fail with HTTP status code "403" + + @issue-ocis-reva-12 @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a new public link share of a folder, with a password and accessing using the public WebDAV API + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "Random data" to "/PARENT/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | PARENT | + | password | %public% | + | permissions | change | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | folder | + | mimetype | httpd/unix-directory | + | file_target | /PARENT | + | path | /PARENT | + | permissions | change | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the old public WebDAV API with password "%public%" and the content should be "Random data" + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "%public%" and the content should be "Random data" + But the public should not be able to download file "/randomfile.txt" from inside the last public link shared folder using the old public WebDAV API without a password + And the public should not be able to download file "/randomfile.txt" from inside the last public link shared folder using the old public WebDAV API with password "%regular%" + And the public should not be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API without a password + And the public should not be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API with password "%regular%" + @issue-ocis-2079 @issue-ocis-reva-292 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @issue-ocis-reva-294 + Scenario Outline: Getting the share information of public link share from the OCS API does not expose sensitive information + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + | password | %public% | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | item_type | file | + | share_type | public_link | + | permissions | read | + | uid_owner | Alice | + | share_with | ***redacted*** | + | share_with_displayname | ***redacted*** | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Getting the share information of passwordless public-links hides credential placeholders + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | item_type | file | + | share_type | public_link | + | permissions | read | + | uid_owner | %username% | + And the fields of the last response should not include + | share_with | ANY_VALUE | + | share_with_displayname | ANY_VALUE | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a link share with no specified permissions defaults to read permissions when public upload is disabled globally and accessing using the public WebDAV API + Given using OCS API version "" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | permissions | read | + And the public upload to the last publicly shared folder using the old public WebDAV API should fail with HTTP status code "403" + And the public upload to the last publicly shared folder using the new public WebDAV API should fail with HTTP status code "403" + + @issue-ocis-reva-41 @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a public link share with read+create permissions is forbidden when public upload is disabled globally + Given using OCS API version "" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | read,create | + Then the OCS status code should be "" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 403 | + | 2 | 403 | + + + Scenario Outline: Creating a public link share with create permissions is forbidden when public upload is disabled globally + Given using OCS API version "" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | create | + Then the OCS status code should be "" + + @notToImplementOnOCIS @issue-ocis-2079 @issue-ocis-reva-41 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 403 | + | 2 | 403 | + + + Scenario Outline: Updating a public link share with read+create permissions is forbidden when public upload is disabled globally + Given using OCS API version "" + And user "Alice" has created folder "/afolder" + And user "Alice" has created a public link share with settings + | path | /afolder | + | permissions | read | + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + When user "Alice" tries to update the last public link share using the sharing API with + | permissions | read,create | + Then the OCS status code should be "" + + @notToImplementOnOCIS @issue-ocis-2079 @issue-ocis-reva-41 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 403 | + | 2 | 403 | + + @issue-ocis-reva-41 + Scenario Outline: Creating a link share with read+update+create permissions is forbidden when public upload is disabled globally + Given using OCS API version "" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | read,update,create | + Then the OCS status code should be "" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 403 | + | 2 | 403 | + + @issue-ocis-reva-41 @skipOnOcis + Scenario Outline: Creating a link share with update permissions defaults to read permissions when public upload disabled globally + Given using OCS API version "" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | read,update,create,delete | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the last response should be empty + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | + + + Scenario Outline: Creating a link share with edit permissions keeps it using the public WebDAV API + Given using OCS API version "" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | read,update,create,delete | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | permissions | read,update,create,delete | + And uploading a file should work using the old public WebDAV API + And uploading a file should work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a link share with upload permissions keeps it using the public WebDAV API + Given using OCS API version "" + And user "Alice" has created folder "/afolder" + When user "Alice" creates a public link share using the sharing API with settings + | path | /afolder | + | permissions | read,create | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | permissions | read,create | + And uploading a file should work using the old public WebDAV API + And uploading a file should work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + @issue-ocis-reva-283 @notToImplementOnOCIS + Scenario Outline: Do not allow public sharing of the root on ownCloud10 + Given using OCS API version "" + When user "Alice" creates a public link share using the sharing API with settings + | path | / | + Then the OCS status code should be "" + And the HTTP status code should be "" + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | + + @issue-ocis-reva-283 @skipOnOcV10 + Scenario Outline: Allow public sharing of the root on OCIS when the default permission is read and access using the public WebDAV API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | / | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | folder | + | mimetype | httpd/unix-directory | + | file_target | / | + | path | / | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "Random data" + And the public upload to the last publicly shared folder using the new public WebDAV API should fail with HTTP status code "403" + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + Scenario Outline: user creates a public link share of a file with file name longer than 64 chars using the public WebDAV API + Given using OCS API version "" + And user "Alice" has uploaded file with content "long file" to "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | /aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog.txt | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download the last publicly shared file using the old public WebDAV API without a password and the content should be "long file" + And the public should be able to download the last publicly shared file using the new public WebDAV API without a password and the content should be "long file" + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + + Scenario Outline: user creates a public link share of a folder with folder name longer than 64 chars and access using the public WebDAV API + Given using OCS API version "" + And user "Alice" has created folder "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog" + And user "Alice" has uploaded file with content "Random data" to "/aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | /aquickbrownfoxjumpsoveraverylazydogaquickbrownfoxjumpsoveralazydog | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the old public WebDAV API without password and the content should be "Random data" + And the public should be able to download file "/randomfile.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "Random data" + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-41 + Scenario Outline: Create a public link with default expiration date set and max expiration date enforced and access using the public WebDAV API + Given using OCS API version "" + And parameter "shareapi_default_expire_date" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date" of app "core" has been set to "yes" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the fields of the last response to user "Alice" should include + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | item_type | file | + | share_type | public_link | + | permissions | read | + | uid_owner | %username% | + | expiration | +7 days | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And the fields of the last response to user "Alice" should include + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | item_type | file | + | share_type | public_link | + | permissions | read | + | uid_owner | %username% | + | expiration | +7 days | + And the public should be able to download the last publicly shared file using the old public WebDAV API without a password and the content should be "Random data" + And the public should be able to download the last publicly shared file using the new public WebDAV API without a password and the content should be "Random data" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @issue-ocis-reva-199 + Scenario Outline: Delete a folder that has been publicly shared and try to access using the public WebDAV API + Given user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file with content "Random data" to "/PARENT/parent.txt" + And user "Alice" has created a public link share with settings + | path | PARENT | + | permissions | read | + When user "Alice" deletes folder "PARENT" using the WebDAV API + And the public download of file "/parent.txt" from inside the last public link shared folder using the public WebDAV API should fail with HTTP status code "404" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + @issue-ocis-reva-292 + Scenario Outline: try to download from a public share that has upload only permissions using the public webdav api + Given user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file with content "Random data" to "/PARENT/parent.txt" + And user "Alice" has created a public link share with settings + | path | PARENT | + | permissions | uploadwriteonly | + When the public downloads file "parent.txt" from inside the last public link shared folder using the public WebDAV API + Then the value of the item "//s:message" in the response should be "" + And the HTTP status code should be "404" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | response | + | old | | + + + Examples: + | public-webdav-api-version | response | + | new | File not found: parent.txt | + + + Scenario: Get the size of a file shared by public link + Given user "Alice" has uploaded file with content "This is a test file" to "test-file.txt" + And user "Alice" has created a public link share with settings + | path | test-file.txt | + | permissions | read | + When the public gets the size of the last shared public link using the WebDAV API + Then the HTTP status code should be "207" + And the size of the file should be "19" + + + Scenario Outline: Get the mtime of a file shared by public link + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API + When user "Alice" creates a public link share using the sharing API with settings + | path | file.txt | + | permissions | read | + Then the HTTP status code should be "200" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Get the mtime of a file inside a folder shared by public link + Given using DAV path + And user "Alice" has created folder "testFolder" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "testFolder/file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API + When user "Alice" creates a public link share using the sharing API with settings + | path | /testFolder | + | permissions | read | + Then the HTTP status code should be "200" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @issue-37605 @skipOnOcV10 + Scenario: Get the mtime of a file inside a folder shared by public link using new webDAV version + Given user "Alice" has created folder "testFolder" + And user "Alice" has created a public link share with settings + | path | /testFolder | + | permissions | read,update,create,delete | + When the public uploads file "file.txt" to the last public link shared folder with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "testFolder/file.txt" should exist + And as "Alice" the mtime of the file "testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" + + @issue-37605 @skipOnOcV10 + Scenario: overwriting a file changes its mtime (new public webDAV API) + Given user "Alice" has created folder "testFolder" + When user "Alice" uploads file with content "uploaded content for file name ending with a dot" to "testFolder/file.txt" using the WebDAV API + And user "Alice" creates a public link share using the sharing API with settings + | path | /testFolder | + | permissions | read,update,create,delete | + And the public uploads file "file.txt" to the last public link shared folder with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the new public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/testFolder/file.txt" should exist + And as "Alice" the mtime of the file "testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" + + @notToImplementOnOCIS + Scenario Outline: Set multiple expiration dates, the expired date shouldn't affect the future expiration date + Given using OCS API version "" + And parameter "shareapi_enforce_expire_date" of app "core" has been set to "yes" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | /textfile0.txt | + | name | link1 | + | expireDateAsString | +6 days | + And user "Alice" creates a public link share using the sharing API with settings + | path | /textfile0.txt | + | name | link2 | + | expireDateAsString | +5 days | + And the administrator expires the last created public link share using the testing API + Then the HTTP status code should be "" + When user "Alice" gets all the shares from the file "textfile0.txt" using the sharing API + Then the HTTP status code should be "" + And the OCS status code should be "" + And the fields of the last response to user "Alice" should include + | name | link1 | + | expiration | +6 days | + But the fields of the last response should not include + | name | link2 | + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 200 | diff --git a/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareOc10Issue37605.feature b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareOc10Issue37605.feature new file mode 100644 index 00000000000..22fd35fbc80 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareOc10Issue37605.feature @@ -0,0 +1,31 @@ +@api @files_sharing-app-required @public_link_share-feature-required @notToImplementOnOCIS + +Feature: create a public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "testFolder" + And user "Alice" has created a public link share with settings + | path | /testFolder | + | permissions | read,update,create,delete | + + @issue-37605 + Scenario: Get the mtime of a file inside a folder shared by public link using new webDAV version + When the public uploads file "file.txt" to the last public link shared folder with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "testFolder/file.txt" should exist + And as "Alice" the mtime of the file "testFolder/file.txt" should not be "Thu, 08 Aug 2019 04:18:13 GMT" +# And as "Alice" the mtime of the file "testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should not be "Thu, 08 Aug 2019 04:18:13 GMT" +# And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" + + @issue-37605 + Scenario: overwriting a file changes its mtime (new public webDAV API) + Given user "Alice" has uploaded file with content "uploaded content for file name ending with a dot" to "testFolder/file.txt" + When the public uploads file "file.txt" to the last public link shared folder with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the new public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/testFolder/file.txt" should exist + And as "Alice" the mtime of the file "testFolder/file.txt" should not be "Thu, 08 Aug 2019 04:18:13 GMT" +# And as "Alice" the mtime of the file "testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + And the mtime of file "file.txt" in the last shared public link using the WebDAV API should not be "Thu, 08 Aug 2019 04:18:13 GMT" +# And the mtime of file "file.txt" in the last shared public link using the WebDAV API should be "Thu, 08 Aug 2019 04:18:13 GMT" \ No newline at end of file diff --git a/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareToShares.feature b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareToShares.feature new file mode 100644 index 00000000000..5b6be928858 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShareToShares.feature @@ -0,0 +1,32 @@ +@api @files_sharing-app-required @public_link_share-feature-required +Feature: create a public link share when share_folder is set to Shares + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Creating a new public link share of a file gives the correct response + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + When user "Alice" creates a public link share using the sharing API with settings + | path | randomfile.txt | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /randomfile.txt | + | path | /randomfile.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiSharePublicLink1/deletePublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink1/deletePublicLinkShare.feature new file mode 100644 index 00000000000..61612e977dc --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink1/deletePublicLinkShare.feature @@ -0,0 +1,72 @@ +@api +Feature: delete a public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @issue-product-60 + Scenario Outline: Deleting a public link of a file + Given using OCS API version "" + And user "Alice" has uploaded file with content "This is a test file" to "test-file.txt" + And user "Alice" has created a public link share with settings + | path | test-file.txt | + | name | sharedlink | + When user "Alice" deletes public link share named "sharedlink" in file "test-file.txt" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And as user "Alice" the file "test-file.txt" should not have any shares + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-product-60 @issue-ocis-reva-311 + Scenario Outline: Deleting a public link after renaming a file + Given using OCS API version "" + And user "Alice" has uploaded file with content "This is a test file" to "test-file.txt" + And user "Alice" has created a public link share with settings + | path | test-file.txt | + | name | sharedlink | + When user "Alice" moves file "/test-file.txt" to "/renamed-test-file.txt" using the WebDAV API + And user "Alice" deletes public link share named "sharedlink" in file "renamed-test-file.txt" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as user "Alice" the file "renamed-test-file.txt" should not have any shares + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-product-60 + Scenario Outline: Deleting a public link of a folder + Given using OCS API version "" + And user "Alice" has created folder "test-folder" + And user "Alice" has created a public link share with settings + | path | /test-folder | + | name | sharedlink | + When user "Alice" deletes public link share named "sharedlink" in folder "test-folder" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And as user "Alice" the folder "test-folder" should not have any shares + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-product-60 + Scenario Outline: Deleting a public link of a file in a folder + Given using OCS API version "" + And user "Alice" has created folder "test-folder" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/test-folder/testfile.txt" using the WebDAV API + And user "Alice" has created a public link share with settings + | path | /test-folder/testfile.txt | + | name | sharedlink | + And user "Alice" deletes public link share named "sharedlink" in file "/test-folder/testfile.txt" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And as user "Alice" the file "/test-folder/testfile.txt" should not have any shares + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + diff --git a/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature b/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature new file mode 100644 index 00000000000..95dfc7f4327 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature @@ -0,0 +1,190 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-310 +Feature: copying from public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/PARENT" + + + Scenario: Copy file within a public link folder new public WebDAV API + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT/copy1.txt" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/copy1.txt" for user "Alice" should be "some data" + + + Scenario: Copy folder within a public link folder new public WebDAV API + Given user "Alice" has created folder "/PARENT/testFolder" + And user "Alice" has uploaded file with content "some data" to "/PARENT/testFolder/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies folder "/testFolder" to "/testFolder-copy" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "/PARENT/testFolder" should exist + And as "Alice" folder "/PARENT/testFolder-copy" should exist + And the content of file "/PARENT/testFolder/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/testFolder-copy/testfile.txt" for user "Alice" should be "some data" + + + Scenario: Copy file within a public link folder to a new folder + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created folder "/PARENT/testFolder" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/testFolder/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT/testFolder/copy1.txt" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/testFolder/copy1.txt" for user "Alice" should be "some data" + + + Scenario: Copy file within a public link folder to same file name as already existing one + Given user "Alice" has uploaded file with content "some data 0" to "/PARENT/testfile.txt" + And user "Alice" has uploaded file with content "some data 1" to "/PARENT/copy1.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT/copy1.txt" should exist + And the content of file "/PARENT/copy1.txt" for user "Alice" should be "some data 0" + + @skipOnOcV10 @issue-ocis-reva-373 @issue-37683 + Scenario: Copy folder within a public link folder to the same folder name as an already existing file + Given user "Alice" has created folder "/PARENT/testFolder" + And user "Alice" has uploaded file with content "some data" to "/PARENT/testFolder/testfile.txt" + And user "Alice" has uploaded file with content "some data 1" to "/PARENT/copy1.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies folder "/testFolder" to "/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/PARENT/testFolder" should exist + And as "Alice" folder "/PARENT/copy1.txt" should exist + And as "Alice" file "/PARENT/copy1.txt/testfile.txt" should not exist + And the content of file "/PARENT/testFolder/testfile.txt" for user "Alice" should be "some data" + + + Scenario: Copy file within a public link folder and delete file + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/copy1.txt" using the new public WebDAV API + And user "Alice" deletes file "/PARENT/copy1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/PARENT/copy1.txt" should not exist + + @skipOnOcV10 @issue-ocis-reva-373 @issue-37683 + Scenario: Copy file within a public link folder to a file with name same as an existing folder + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created folder "/PARENT/new-folder" + And user "Alice" has uploaded file with content "some data 1" to "/PARENT/new-folder/testfile1.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/new-folder" using the new public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" folder "/PARENT/new-folder" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/new-folder/testfile.txt" for user "Alice" should be "some data" + + + Scenario Outline: Copy file with special characters in it's name within a public link folder + Given user "Alice" has uploaded file with content "some data" to "/PARENT/" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/" to "/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/" should exist + And as "Alice" file "/PARENT/copy1.txt" should exist + And the content of file "/PARENT/" for user "Alice" should be "some data" + And the content of file "/PARENT/copy1.txt" for user "Alice" should be "some data" + Examples: + | file-name | + | नेपाली.txt | + | strängé file.txt | + | C++ file.cpp | + | sample,1.txt | + + + Scenario Outline: Copy file within a public link folder to a file with special characters in it's name + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT/" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/" for user "Alice" should be "some data" + Examples: + | destination-file-name | + | नेपाली.txt | + | strängé file.txt | + | C++ file.cpp | + | sample,1.txt | + + + Scenario Outline: Copy file within a public link folder into a folder with special characters + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created folder "/PARENT/" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "//copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT//copy1.txt" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT//copy1.txt" for user "Alice" should be "some data" + Examples: + | destination-folder-name | + | नेपाली.txt | + | strängé file.txt | + | C++ file.cpp | + | sample,1.txt | + + @issue-ocis-reva-368 + Scenario Outline: Copy file within a public link folder to a file with unusual destination names + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/" using the new public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/PARENT/testfile.txt" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + Examples: + | destination-file-name | + | testfile.txt | + | | + + @issue-ocis-reva-368 + Scenario Outline: Copy folder within a public link folder to a folder with unusual destination names + Given user "Alice" has created folder "/PARENT/testFolder" + And user "Alice" has uploaded file with content "some data" to "/PARENT/testFolder/testfile.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies folder "/testFolder" to "/" using the new public WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/PARENT/testFolder" should exist + And the content of file "/PARENT/testFolder/testfile.txt" for user "Alice" should be "some data" + Examples: + | destination-file-name | + | testFolder | + | | diff --git a/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLinkOc10Issue37683.feature b/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLinkOc10Issue37683.feature new file mode 100644 index 00000000000..cb5f4e005d4 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLinkOc10Issue37683.feature @@ -0,0 +1,37 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-310 @notToImplementOnOCIS +Feature: copying from public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/PARENT" + + @issue-ocis-reva-373 @issue-37683 @skipOnLDAP @issue-user_ldap-702 + Scenario: Copy folder within a public link folder to the same folder name as an already existing file + Given user "Alice" has created folder "/PARENT/testFolder" + And user "Alice" has uploaded file with content "some data" to "/PARENT/testFolder/testfile.txt" + And user "Alice" has uploaded file with content "some data 1" to "/PARENT/copy1.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies folder "/testFolder" to "/copy1.txt" using the new public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/PARENT/testFolder" should exist + And as "Alice" folder "/PARENT/copy1.txt" should exist + And as "Alice" file "/PARENT/copy1.txt/testfile.txt" should exist + And the content of file "/PARENT/copy1.txt/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/testFolder/testfile.txt" for user "Alice" should be "some data" + + @issue-ocis-reva-373 @issue-37683 + Scenario: Copy file within a public link folder to a file with name same as an existing folder + Given user "Alice" has uploaded file with content "some data" to "/PARENT/testfile.txt" + And user "Alice" has created folder "/PARENT/new-folder" + And user "Alice" has uploaded file with content "some data 1" to "/PARENT/new-folder/testfile1.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + When the public copies file "/testfile.txt" to "/new-folder" using the new public WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/PARENT/testfile.txt" should exist + And as "Alice" file "/PARENT/new-folder" should exist + And the content of file "/PARENT/testfile.txt" for user "Alice" should be "some data" + And the content of file "/PARENT/new-folder" for user "Alice" should be "some data" diff --git a/tests/acceptance/features/coreApiSharePublicLink2/multilinkSharing.feature b/tests/acceptance/features/coreApiSharePublicLink2/multilinkSharing.feature new file mode 100644 index 00000000000..1fb94635f37 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/multilinkSharing.feature @@ -0,0 +1,245 @@ +@api @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-288 @issue-ocis-reva-252 +Feature: multilinksharing + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: Creating three public shares of a folder + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink2 | + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink3 | + When user "Alice" updates the last public link share using the sharing API with + | permissions | read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as user "Alice" the public shares of folder "/FOLDER" should be + | path | permissions | name | + | /FOLDER | 15 | sharedlink2 | + | /FOLDER | 15 | sharedlink1 | + | /FOLDER | 1 | sharedlink3 | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating three public shares of a file + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink2 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink3 | + When user "Alice" updates the last public link share using the sharing API with + | permissions | read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as user "Alice" the public shares of file "/textfile0.txt" should be + | path | permissions | name | + | /textfile0.txt | 1 | sharedlink2 | + | /textfile0.txt | 1 | sharedlink1 | + | /textfile0.txt | 1 | sharedlink3 | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Check that updating password doesn't remove name of links + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink2 | + When user "Alice" updates the last public link share using the sharing API with + | password | %alt1% | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as user "Alice" the public shares of folder "/FOLDER" should be + | path | permissions | name | + | /FOLDER | 15 | sharedlink2 | + | /FOLDER | 15 | sharedlink1 | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Deleting a file deletes also its public links + Given using OCS API version "1" + And using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink2 | + And user "Alice" has deleted file "/textfile0.txt" + And the HTTP status code should be "204" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as user "Alice" the file "/textfile0.txt" should not have any shares + Examples: + | dav-path | + | old | + | new | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | + | spaces | + + + Scenario Outline: Deleting one public link share of a file doesn't affect the rest + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink2 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink3 | + When user "Alice" deletes public link share named "sharedlink2" in file "/textfile0.txt" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as user "Alice" the public shares of file "/textfile0.txt" should be + | path | permissions | name | + | /textfile0.txt | 1 | sharedlink1 | + | /textfile0.txt | 1 | sharedlink3 | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Overwriting a file doesn't remove its public shares + Given using OCS API version "1" + And using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | textfile0.txt | + | password | %public% | + | expireDate | +3 days | + | permissions | read | + | name | sharedlink2 | + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the public shares of file "/textfile0.txt" should be + | path | permissions | name | + | /textfile0.txt | 1 | sharedlink1 | + | /textfile0.txt | 1 | sharedlink2 | + Examples: + | dav-path | + | old | + | new | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | + | spaces | + + @issue-ocis-reva-335 + Scenario Outline: Renaming a folder doesn't remove its public shares + Given using OCS API version "1" + And using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink1 | + And user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | expireDate | +3 days | + | publicUpload | true | + | permissions | change | + | name | sharedlink2 | + When user "Alice" moves folder "/FOLDER" to "/FOLDER_RENAMED" using the WebDAV API + Then the HTTP status code should be "201" + And as user "Alice" the public shares of file "/FOLDER_RENAMED" should be + | path | permissions | name | + | /FOLDER_RENAMED | 15 | sharedlink1 | + | /FOLDER_RENAMED | 15 | sharedlink2 | + Examples: + | dav-path | + | old | + | new | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | + | spaces | diff --git a/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToRoot.feature b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToRoot.feature new file mode 100644 index 00000000000..6af5feb6b95 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToRoot.feature @@ -0,0 +1,181 @@ +@api @files_sharing-app-required @public_link_share-feature-required @notToImplementOnOCIS +Feature: reshare as public link + As a user + I want to create public link shares from files/folders shared with me + So that I can give controlled access to others + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: creating a public link from a share with read permission only is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "read" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | publicUpload | false | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with share+read only permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API without password and the content should be "some content" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "some content" + But uploading a file should not work using the old public WebDAV API + But uploading a file should not work using the new public WebDAV API + + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + Scenario Outline: creating an upload public link from a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with read+write permissions only is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "change" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API without password and the content should be "some content" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "some content" + But uploading a file should not work using the old public WebDAV API + But uploading a file should not work using the new public WebDAV API + + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + Scenario Outline: creating an upload public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API without password and the content should be "some content" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API without password and the content should be "some content" + And uploading a file should work using the old public WebDAV API + And uploading a file should work using the new public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating an upload public link from a sub-folder of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + When user "Brian" creates a public link share using the sharing API with settings + | path | /test/sub | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: increasing permissions of a public link of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has created a public link share with settings + | path | /test | + | permissions | read | + | publicUpload | false | + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + And uploading a file should not work using the new public WebDAV API + + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: increasing permissions of a public link from a sub-folder of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has created a public link share with settings + | path | /test/sub | + | permissions | read | + | publicUpload | false | + And uploading a file should not work using the old public WebDAV API + And uploading a file should not work using the new public WebDAV API + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + And uploading a file should not work using the new public WebDAV API + + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | diff --git a/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature new file mode 100644 index 00000000000..135a24ce1bb --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature @@ -0,0 +1,184 @@ +@api @files_sharing-app-required @public_link_share-feature-required +Feature: reshare as public link + As a user + I want to create public link shares from files/folders shared with me + So that I can give controlled access to others + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + + + Scenario Outline: creating a public link from a share with read permission only is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "read" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | false | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with share+read only permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API + And the downloaded content should be "some content" + But uploading a file should not work using the new public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating an upload public link from a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with read+write permissions only is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "change" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: creating a public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API + And the downloaded content should be "some content" + But uploading a file should not work using the new public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating an upload public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the new public WebDAV API + And the downloaded content should be "some content" + And uploading a file should work using the new public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating an upload public link from a sub-folder of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test/sub | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: increasing permissions of a public link of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | permissions | read | + | publicUpload | false | + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" or "403" + And the HTTP status code should be "" or "" + And uploading a file should not work using the new public WebDAV API + Examples: + | ocs_api_version | http_status_code1 | http_status_code2 | + | 1 | 200 | 200 | + | 2 | 404 | 403 | + + + Scenario Outline: increasing permissions of a public link from a sub-folder of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test/sub | + | permissions | read | + | publicUpload | false | + And uploading a file should not work using the new public WebDAV API + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" or "403" + And the HTTP status code should be "" or "" + And uploading a file should not work using the new public WebDAV API + Examples: + | ocs_api_version | http_status_code1 | http_status_code2 | + | 1 | 200 | 200 | + | 2 | 404 | 403 | diff --git a/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesOldDav.feature b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesOldDav.feature new file mode 100644 index 00000000000..7b478454fb1 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesOldDav.feature @@ -0,0 +1,117 @@ +@api @files_sharing-app-required @public_link_share-feature-required @notToImplementOnOCIS @issue-ocis-2079 +Feature: reshare as public link + As a user + I want to create public link shares from files/folders shared with me + So that I can give controlled access to others + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + + + Scenario Outline: creating a public link from a share with share+read only permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API + And the downloaded content should be "some content" + But uploading a file should not work using the old public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating a public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | publicUpload | false | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API + And the downloaded content should be "some content" + But uploading a file should not work using the old public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: creating an upload public link from a share with share+read+write permissions is allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has uploaded file with content "some content" to "/test/file.txt" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + When user "Brian" creates a public link share using the sharing API with settings + | path | /Shares/test | + | permissions | read,update,create,delete | + | publicUpload | true | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download file "file.txt" from inside the last public link shared folder using the old public WebDAV API + And the downloaded content should be "some content" + And uploading a file should work using the old public WebDAV API + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: increasing permissions of a public link of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | permissions | read | + | publicUpload | false | + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: increasing permissions of a public link from a sub-folder of a share with share+read only permissions is not allowed + Given using OCS API version "" + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test/sub" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test/sub | + | permissions | read | + | publicUpload | false | + And uploading a file should not work using the old public WebDAV API + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | diff --git a/tests/acceptance/features/coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature b/tests/acceptance/features/coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature new file mode 100644 index 00000000000..47aea454066 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink3/allowGroupToCreatePublicLinks.feature @@ -0,0 +1,110 @@ +@api @files_sharing-app-required @notToImplementOnOcis +Feature: public share sharers groups setting + As an admin + I should be able to allow only certain groups to create public links + So that random links are not generated on my file system + + Background: + Given group "grp1" has been created + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShare.txt" + + + Scenario: users present in public share shares groups can create new public link shares + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has been added to group "grp1" + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /fileToShare.txt | + | path | /fileToShare.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + + + Scenario: users not present in public share shares groups cannot create a new public link share + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "403" + And the HTTP status code should be "200" + And the OCS status message should be "Public link creation is only possible for certain groups" + + + Scenario: existing links can still be updated by sharers even if they are not present in public share sharers groups + Given user "Alice" has created a public link share with settings + | path | /fileToShare.txt | + | permissions | read | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" updates the last public link share using the sharing API with + | expireDate | +3 days | + Then the HTTP status code should be "200" + And the OCS status code should be "100" + And the fields of the last response to user "Alice" should include + | expiration | +3 days | + + + Scenario: existing links can still be deleted by sharers even if they are not present in public share sharers groups + Given user "Alice" has created a public link share with settings + | path | /fileToShare.txt | + | permissions | read | + | name | shared-link | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" deletes public link share named "shared-link" in file "fileToShare.txt" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "100" + And as user "Alice" the file "fileToShare.txt" should not have any shares + + + Scenario: creating a new link share is not restricted if no groups are inside the allowed public share sharers groups even if allowlist is enabled + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /fileToShare.txt | + | path | /fileToShare.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + + + Scenario: multiple groups can be added to public share sharers groups allow list + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has uploaded file with content "file to share with user" to "/fileToShare.txt" + And user "Carol" has uploaded file with content "file to share with user" to "/fileToShare.txt" + And group "grp2" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1", "grp2"]' + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + And user "Brian" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + And user "Carol" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the HTTP status code of responses on all endpoints should be "200" + And the OCS status code of responses on each endpoint should be "100, 100, 403" respectively + And the OCS status message should be "Public link creation is only possible for certain groups" diff --git a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature new file mode 100644 index 00000000000..f1dc0b51f57 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature @@ -0,0 +1,612 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-252 +Feature: update a public link share + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @issue-37653 @skipOnOcV10 + Scenario Outline: API responds with a full set of parameters when owner changes the expireDate of a public share + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + When user "Alice" updates the last public link share using the sharing API with + | expireDate | +3 days | + Then the OCS status code should be "" + And the OCS status message should be "Ok" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | A_STRING | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | additional_info_owner | | + | additional_info_file_owner | | + | item_type | folder | + | item_source | A_STRING | + | path | /FOLDER | + | mimetype | httpd/unix-directory | + | storage_id | A_STRING | + | storage | A_NUMBER | + | file_source | A_STRING | + | file_target | /FOLDER | + | mail_send | 0 | + | name | | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @smokeTest @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating its expiration date and getting its info + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | expireDate | +3 days | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read | + | stime | A_NUMBER | + | expiration | +3 days | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @notToImplementOnOCIS @issue-39820 + Scenario Outline: API responds with a full set of parameters when owner renames the folder with a public link (bug demonstration) + Given using OCS API version "" + And using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has moved folder "/FOLDER" to "/RENAMED_FOLDER" + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | additional_info_owner | | + | additional_info_file_owner | | + | item_type | folder | + | item_source | A_STRING | + | path | /RENAMED_FOLDER | + | mimetype | httpd/unix-directory | + | storage_id | A_STRING | + | storage | A_STRING | + | file_source | A_STRING | + # uncomment the following line and remove the next one after the issue has been fixed + # | file_target | /RENAMED_FOLDER | + | file_target | /FOLDER | + | mail_send | 0 | + | name | | + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | old | 1 | 100 | + | old | 2 | 200 | + | new | 1 | 100 | + | new | 2 | 200 | + + + Scenario Outline: Creating a new public link share with password and adding an expiration date using public API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has created a public link share with settings + | path | randomfile.txt | + | password | %public% | + When user "Alice" updates the last public link share using the sharing API with + | expireDate | +3 days | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download the last publicly shared file using the old public WebDAV API with password "%public%" and the content should be "Random data" + And the public should be able to download the last publicly shared file using the new public WebDAV API with password "%public%" and the content should be "Random data" + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Creating a new public link share with password and removing (updating) it to make the resources accessible without password using public API + Given using OCS API version "" + And user "Alice" has uploaded file with content "Random data" to "/randomfile.txt" + And user "Alice" has created a public link share with settings + | path | randomfile.txt | + | password | %public% | + When user "Alice" updates the last public link share using the sharing API with + #removing password is basically making password empty + | password | %remove% | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the public should be able to download the last publicly shared file using the old public WebDAV API without a password and the content should be "Random data" + And the public should be able to download the last publicly shared file using the new public WebDAV API without a password and the content should be "Random data" + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating its expiration date and getting its info (ocis Bug demonstration) + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | expireDate | +3 days | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read | + | stime | A_NUMBER | + | expiration | +3 days | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating its password and getting its info + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | password | %public% | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read | + | stime | A_NUMBER | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating its permissions and getting its info + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | permissions | read,update,create,delete | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read,update,create,delete | + | stime | A_NUMBER | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating its permissions to view download and upload and getting its info + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | permissions | read,update,create,delete | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read,update,create,delete | + | stime | A_NUMBER | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-336 + Scenario Outline: Creating a new public link share, updating publicUpload option and getting its info + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has updated the last public link share with + | publicUpload | true | + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | public_link | + | file_source | A_STRING | + | file_target | /FOLDER | + | permissions | read,update,create,delete | + | stime | A_NUMBER | + | token | A_TOKEN | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | url | AN_URL | + | mimetype | httpd/unix-directory | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Adding public upload to a read only shared folder as recipient is not allowed using the public API + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | publicUpload | false | + When user "Brian" updates the last public link share using the sharing API with + | publicUpload | true | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + And uploading a file should not work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + Scenario Outline: Adding public upload to a shared folder as recipient is allowed with permissions using the public API + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | publicUpload | false | + When user "Brian" updates the last public link share using the sharing API with + | publicUpload | true | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And uploading a file should work using the old public WebDAV API + And uploading a file should work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Adding public link with all permissions to a read only shared folder as recipient is not allowed using the public API + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | permissions | read | + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And uploading a file should not work using the old public WebDAV API + And uploading a file should not work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: Adding public link with all permissions to a read only shared folder as recipient is allowed with permissions using the public API + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/test" offered by user "Alice" + And user "Brian" has created a public link share with settings + | path | /Shares/test | + | permissions | read | + When user "Brian" updates the last public link share using the sharing API with + | permissions | read,update,create,delete | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And uploading a file should work using the old public WebDAV API + And uploading a file should work using the new public WebDAV API + + @issue-ocis-2079 + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Updating share permissions from change to read restricts public from deleting files using the public API + Given using OCS API version "" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/CHILD/child.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read,update,create,delete | + And user "Alice" has updated the last public link share with + | permissions | read | + When the public deletes file "CHILD/child.txt" from the last public link share using the old public WebDAV API + And the public deletes file "CHILD/child.txt" from the last public link share using the new public WebDAV API + And the HTTP status code of responses on all endpoints should be "403" + And as "Alice" file "PARENT/CHILD/child.txt" should exist + + @issue-ocis-2079 @issue-ocis-reva-292 + Examples: + | ocs_api_version | + | 1 | + | 2 | + + + Scenario Outline: Updating share permissions from read to change allows public to delete files using the public API + Given using OCS API version "" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/parent.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/CHILD/child.txt" + And user "Alice" has created a public link share with settings + | path | /PARENT | + | permissions | read | + And user "Alice" has updated the last public link share with + | permissions | read,update,create,delete | + When the public deletes file "CHILD/child.txt" from the last public link share using the public WebDAV API + And the public deletes file "parent.txt" from the last public link share using the public WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" file "PARENT/CHILD/child.txt" should not exist + And as "Alice" file "PARENT/parent.txt" should not exist + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | ocs_api_version | public-webdav-api-version | + | 1 | old | + | 2 | old | + + + Examples: + | ocs_api_version | public-webdav-api-version | + | 1 | new | + | 2 | new | + + @skipOnOcV10 + Scenario Outline: API responds with a full set of parameters when owner renames the folder with a public link in ocis + Given using OCS API version "" + And using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And user "Alice" has moved folder "/FOLDER" to "/RENAMED_FOLDER" + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | item_type | folder | + | item_source | A_STRING | + | path | /RENAMED_FOLDER | + | mimetype | httpd/unix-directory | + | storage_id | A_STRING | + | storage | A_STRING | + | file_source | A_STRING | + | file_target | /RENAMED_FOLDER | + | mail_send | 0 | + | name | | + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | old | 1 | 100 | + | old | 2 | 200 | + | new | 1 | 100 | + | new | 2 | 200 | + + @personalSpace + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | spaces | 1 | 100 | + | spaces | 2 | 200 | + + @notToImplementOnOCIS @issue-39820 + Scenario Outline: API responds with a full set of parameters when owner renames the file with a public link (bug demonstration) + Given using OCS API version "" + And using DAV path + And user "Alice" has uploaded file with content "some content" to "/lorem.txt" + And user "Alice" has created a public link share with settings + | path | lorem.txt | + And user "Alice" has moved file "/lorem.txt" to "/new-lorem.txt" + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | additional_info_owner | | + | additional_info_file_owner | | + | item_type | file | + | item_source | A_STRING | + | path | /new-lorem.txt | + | mimetype | text/plain | + | storage_id | A_STRING | + | storage | A_STRING | + | file_source | A_STRING | + # uncomment the following line and remove the next one after the issue has been fixed + # | file_target | /new-lorem.txt | + | file_target | /lorem.txt | + | mail_send | 0 | + | name | | + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | old | 1 | 100 | + | old | 2 | 200 | + | new | 1 | 100 | + | new | 2 | 200 | + + @skipOnOcV10 + Scenario Outline: API responds with a full set of parameters when owner renames the file with a public link in ocis + Given using OCS API version "" + And using DAV path + And user "Alice" has uploaded file with content "some content" to "/lorem.txt" + And user "Alice" has created a public link share with settings + | path | lorem.txt | + And user "Alice" has moved file "/lorem.txt" to "/new-lorem.txt" + When user "Alice" gets the info of the last public link share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | item_type | file | + | item_source | A_STRING | + | path | /new-lorem.txt | + | mimetype | text/plain | + | storage_id | A_STRING | + | storage | A_STRING | + | file_source | A_STRING | + | file_target | /new-lorem.txt | + | mail_send | 0 | + | name | | + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | old | 1 | 100 | + | old | 2 | 200 | + | new | 1 | 100 | + | new | 2 | 200 | + + @personalSpace + Examples: + | dav-path | ocs_api_version | ocs_status_code | + | spaces | 1 | 100 | + | spaces | 2 | 200 | diff --git a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShareOc10Issue37653.feature b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShareOc10Issue37653.feature new file mode 100644 index 00000000000..bea3766b622 --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShareOc10Issue37653.feature @@ -0,0 +1,47 @@ +@api @files_sharing-app-required @public_link_share-feature-required @notToImplementOnOCIS +Feature: update a public link share + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @issue-37653 + Scenario Outline: API responds with a full set of parameters when owner changes the expireDate of a public share + Given using OCS API version "" + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share with settings + | path | FOLDER | + When user "Alice" updates the last public link share using the sharing API with + | expireDate | +3 days | + Then the OCS status code should be "" + And the OCS status message should be "" + #And the OCS status message should be "Ok" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | id | A_STRING | + | share_type | public_link | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | read | + | stime | A_NUMBER | + | parent | | + | expiration | A_STRING | + | token | A_STRING | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | additional_info_owner | | + | additional_info_file_owner | | + | item_type | folder | + | item_source | A_STRING | + | path | /FOLDER | + | mimetype | httpd/unix-directory | + | storage_id | A_STRING | + | storage | A_NUMBER | + | file_source | A_STRING | + | file_target | /FOLDER | + | mail_send | 0 | + | name | | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature new file mode 100644 index 00000000000..bedf7e7199c --- /dev/null +++ b/tests/acceptance/features/coreApiSharePublicLink3/uploadToPublicLinkShare.feature @@ -0,0 +1,284 @@ +@api @files_sharing-app-required @public_link_share-feature-required @issue-ocis-reva-315 @issue-ocis-reva-316 + +Feature: upload to a public link share + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + + @smokeTest @notToImplementOnOCIS @issue-ocis-2079 + Scenario: Uploading same file to a public upload-only share multiple times via old API + # The old API needs to have the header OC-Autorename: 1 set to do the autorename + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + When the public uploads file "test.txt" with content "test" using the old public WebDAV API + And the public uploads file "test.txt" with content "test2" with auto-rename mode using the old public WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" + And the content of file "/FOLDER/test (2).txt" for user "Alice" should be "test2" + + @smokeTest @issue-ocis-reva-286 + Scenario: Uploading same file to a public upload-only share multiple times via new API + # The new API does the autorename automatically in upload-only folders + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + When the public uploads file "test.txt" with content "test" using the new public WebDAV API + And the public uploads file "test.txt" with content "test2" using the new public WebDAV API + Then the HTTP status code of responses on all endpoints should be "201" + And the following headers should match these regular expressions + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" + And the content of file "/FOLDER/test (2).txt" for user "Alice" should be "test2" + + + Scenario Outline: Uploading file to a public upload-only share using public API that was deleted does not work + Given using DAV path + And user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + And user "Alice" has deleted folder "/FOLDER" + When the public uploads file "test.txt" with content "test" using the public WebDAV API + And the HTTP status code should be "404" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | dav-path | public-webdav-api-version | + | old | old | + | new | old | + + @issue-ocis-reva-290 + Examples: + | dav-path | public-webdav-api-version | + | old | new | + | new | new | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | public-webdav-api-version | + | spaces | new | + + + Scenario Outline: Uploading file to a public read-only share folder with public API does not work + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | read | + When the public uploads file "test.txt" with content "test" using the public WebDAV API + And the HTTP status code should be "403" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-292 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading to a public upload-only share with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test-file" + And the following headers should match these regular expressions + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading to a public upload-only share with password with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | permissions | create | + When the public uploads file "test.txt" with password "%public%" and content "test-file" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test-file" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading to a public read/write share with password with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | password | %public% | + | permissions | change | + When the public uploads file "test.txt" with password "%public%" and content "test-file" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test-file" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading file to a public shared folder with read/write permission when the sharer has insufficient quota does not work with public API + When user "Alice" creates a public link share using the sharing API with settings + | path | FOLDER | + | permissions | change | + And the quota of user "Alice" has been set to "0" + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "507" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-195 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading file to a public shared folder with upload-only permission when the sharer has insufficient quota does not work with public API + When user "Alice" creates a public link share using the sharing API with settings + | path | FOLDER | + | permissions | create | + And the quota of user "Alice" has been set to "0" + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "507" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-195 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading file to a public shared folder does not work when allow public uploads has been disabled after sharing the folder with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "403" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + | new | + + + Scenario Outline: Uploading file to a public shared folder does not work when allow public uploads has been disabled before sharing and again enabled after sharing the folder with public API + Given parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And user "Alice" has created a public link share with settings + | path | FOLDER | + And parameter "shareapi_allow_public_upload" of app "core" has been set to "yes" + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + And the HTTP status code should be "403" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-41 + Examples: + | public-webdav-api-version | + | new | + + + Scenario Outline: Uploading file to a public shared folder works when allow public uploads has been disabled and again enabled after sharing the folder with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | create | + And parameter "shareapi_allow_public_upload" of app "core" has been set to "no" + And parameter "shareapi_allow_public_upload" of app "core" has been set to "yes" + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test-file" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + @issue-ocis-reva-41 + Examples: + | public-webdav-api-version | + | new | + + @smokeTest + Scenario Outline: Uploading to a public upload-write and no edit and no overwrite share with public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | uploadwriteonly | + When the public uploads file "test.txt" with content "test-file" using the public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test-file" + + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | public-webdav-api-version | + | old | + + + Examples: + | public-webdav-api-version | + | new | + + @smokeTest @notToImplementOnOCIS @issue-ocis-2079 + Scenario: Uploading same file to a public upload-write and no edit and no overwrite share multiple times with old public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | uploadwriteonly | + When the public uploads file "test.txt" with content "test" using the old public WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + When the public uploads file "test.txt" with content "test2" using the old public WebDAV API + # Uncomment these once the issue is fixed + # Then the HTTP status code should be "201" + # And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" + # And the content of file "/FOLDER/test (2).txt" for user "Alice" should be "test2" + Then the HTTP status code should be "403" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" + + @smokeTest @issue-ocis-reva-286 + Scenario: Uploading same file to a public upload-write and no edit and no overwrite share multiple times with new public API + Given user "Alice" has created a public link share with settings + | path | FOLDER | + | permissions | uploadwriteonly | + When the public uploads file "test.txt" with content "test" using the new public WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + When the public uploads file "test.txt" with content "test2" using the new public WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" + And the content of file "/FOLDER/test (2).txt" for user "Alice" should be "test2" diff --git a/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature b/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature new file mode 100644 index 00000000000..17b615b79f3 --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature @@ -0,0 +1,303 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + + @smokeTest + Scenario Outline: User is not allowed to reshare file when reshare permission is not given + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions "read,update" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" file "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + But as "Brian" file "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: User is not allowed to reshare folder when reshare permission is not given + Given using OCS API version "" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read,update" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" shares folder "/Shares/FOLDER" with user "Carol" with permissions "read,update" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/FOLDER" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + But as "Brian" folder "/Shares/FOLDER" should exist + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + @smokeTest + Scenario Outline: User is allowed to reshare file with the same permissions + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions "share,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And as "Carol" file "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: User is allowed to reshare folder with the same permissions + Given using OCS API version "" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" shares folder "/Shares/FOLDER" with user "Carol" with permissions "share,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/FOLDER" offered by user "Brian" + And as "Carol" folder "/Shares/FOLDER" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: User is allowed to reshare file with less permissions + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "share,update,read" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions "share,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And as "Carol" file "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: User is allowed to reshare folder with less permissions + Given using OCS API version "" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "share,update,read" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" shares folder "/Shares/FOLDER" with user "Carol" with permissions "share,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/FOLDER" offered by user "Brian" + And as "Carol" folder "/Shares/FOLDER" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: User is not allowed to reshare file and set more permissions bits + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" file "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + But as "Brian" file "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | http_status_code | received_permissions | reshare_permissions | + # passing on more bits including reshare + | 1 | 200 | 17 | 19 | + | 2 | 404 | 17 | 19 | + | 1 | 200 | 17 | 23 | + | 2 | 404 | 17 | 23 | + | 1 | 200 | 17 | 31 | + | 2 | 404 | 17 | 31 | + # passing on more bits but not reshare + | 1 | 200 | 17 | 3 | + | 2 | 404 | 17 | 3 | + | 1 | 200 | 17 | 7 | + | 2 | 404 | 17 | 7 | + | 1 | 200 | 17 | 15 | + | 2 | 404 | 17 | 15 | + + + Scenario Outline: User is allowed to reshare file and set create (4) or delete (8) permissions bits, which get ignored + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the fields of the last response to user "Brian" sharing with user "Carol" should include + | share_with | %username% | + | file_target | /Shares/textfile0.txt | + | path | /Shares/textfile0.txt | + | permissions | | + | uid_owner | %username% | + And as "Carol" file "/Shares/textfile0.txt" should exist + # The receiver of the reshare can always delete their received share, even though they do not have delete permission + And user "Carol" should be able to delete file "/Shares/textfile0.txt" + # But the upstream sharers will still have the file + But as "Brian" file "/Shares/textfile0.txt" should exist + And as "Alice" file "/textfile0.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | received_permissions | reshare_permissions | granted_permissions | + | 1 | 100 | 19 | 23 | 19 | + | 2 | 200 | 19 | 23 | 19 | + | 1 | 100 | 19 | 31 | 19 | + | 2 | 200 | 19 | 31 | 19 | + | 1 | 100 | 19 | 7 | 3 | + | 2 | 200 | 19 | 7 | 3 | + | 1 | 100 | 19 | 15 | 3 | + | 2 | 200 | 19 | 15 | 3 | + | 1 | 100 | 17 | 21 | 17 | + | 2 | 200 | 17 | 21 | 17 | + | 1 | 100 | 17 | 5 | 1 | + | 2 | 200 | 17 | 5 | 1 | + | 1 | 100 | 17 | 25 | 17 | + | 2 | 200 | 17 | 25 | 17 | + | 1 | 100 | 17 | 9 | 1 | + | 2 | 200 | 17 | 9 | 1 | + + + Scenario Outline: User is not allowed to reshare folder and set more permissions bits + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has shared folder "/PARENT" with user "Brian" with permissions + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" shares folder "/Shares/PARENT" with user "Carol" with permissions using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/PARENT" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + But as "Brian" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | http_status_code | received_permissions | reshare_permissions | + # try to pass on more bits including reshare + | 1 | 200 | 17 | 19 | + | 2 | 404 | 17 | 19 | + | 1 | 200 | 17 | 21 | + | 2 | 404 | 17 | 21 | + | 1 | 200 | 17 | 23 | + | 2 | 404 | 17 | 23 | + | 1 | 200 | 17 | 31 | + | 2 | 404 | 17 | 31 | + | 1 | 200 | 19 | 23 | + | 2 | 404 | 19 | 23 | + | 1 | 200 | 19 | 31 | + | 2 | 404 | 19 | 31 | + # try to pass on more bits but not reshare + | 1 | 200 | 17 | 3 | + | 2 | 404 | 17 | 3 | + | 1 | 200 | 17 | 5 | + | 2 | 404 | 17 | 5 | + | 1 | 200 | 17 | 7 | + | 2 | 404 | 17 | 7 | + | 1 | 200 | 17 | 15 | + | 2 | 404 | 17 | 15 | + | 1 | 200 | 19 | 7 | + | 2 | 404 | 19 | 7 | + | 1 | 200 | 19 | 15 | + | 2 | 404 | 19 | 15 | + + + Scenario Outline: User is not allowed to reshare folder and add delete permission bit (8) + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has shared folder "/PARENT" with user "Brian" with permissions + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" shares folder "/Shares/PARENT" with user "Carol" with permissions using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/PARENT" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + But as "Brian" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | http_status_code | received_permissions | reshare_permissions | + # try to pass on extra delete (including reshare) + | 1 | 200 | 17 | 25 | + | 2 | 404 | 17 | 25 | + | 1 | 200 | 19 | 27 | + | 2 | 404 | 19 | 27 | + | 1 | 200 | 23 | 31 | + | 2 | 404 | 23 | 31 | + # try to pass on extra delete (but not reshare) + | 1 | 200 | 17 | 9 | + | 2 | 404 | 17 | 9 | + | 1 | 200 | 19 | 11 | + | 2 | 404 | 19 | 11 | + | 1 | 200 | 23 | 15 | + | 2 | 404 | 23 | 15 | + + + Scenario Outline: Reshare a file with same name as a deleted file + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Alice" has deleted file "textfile0.txt" + And user "Alice" has uploaded file with content "ownCloud new test text file 0" to "/textfile0.txt" + When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/textfile0.txt" offered by user "Alice" + And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud new test text file 0" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Reshare a folder with same name as a deleted folder + Given using OCS API version "" + And user "Alice" has created folder "/PARENT" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Alice" has deleted folder "PARENT" + And user "Alice" has created folder "/PARENT" + When user "Alice" shares folder "PARENT" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/PARENT" offered by user "Alice" + And as "Brian" folder "/Shares/PARENT" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Reshare a folder with same name as a deleted file + Given using OCS API version "" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has created folder "/textfile0.txt" + When user "Alice" shares folder "textfile0.txt" with user "Brian" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/textfile0.txt" offered by user "Alice" + And as "Brian" folder "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareReshareToShares2/reShareChain.feature b/tests/acceptance/features/coreApiShareReshareToShares2/reShareChain.feature new file mode 100644 index 00000000000..62cbc474a7a --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares2/reShareChain.feature @@ -0,0 +1,45 @@ +@api @files_sharing-app-required @issue-ocis-2141 +Feature: resharing can be done on a reshared resource + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + | David | + + @notToImplementOnOCIS + Scenario Outline: Reshared files can be still accessed if a user in the middle removes it. + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has moved file "/Shares/textfile0.txt" to "/textfile0_shared.txt" + And user "Brian" has shared file "/textfile0_shared.txt" with user "Carol" + And user "Carol" has accepted share "" offered by user "Brian" + And user "Carol" has shared file "/Shares/textfile0_shared.txt" with user "David" + And user "David" has accepted share "" offered by user "Carol" + When user "Brian" deletes file "/textfile0_shared.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/Shares/textfile0_shared.txt" for user "Carol" should be "ownCloud test text file 0" + And the content of file "/Shares/textfile0_shared.txt" for user "David" should be "ownCloud test text file 0" + Examples: + | pending_share_path | + | /textfile0_shared.txt | + | /textfile0_shared.txt | + + @skipOnOcV10 + Scenario: Reshared files can be still accessed if a user in the middle removes it. + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has shared file "/Shares/textfile0.txt" with user "Carol" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Brian" + And user "Carol" has shared file "/Shares/textfile0.txt" with user "David" + And user "David" has accepted share "/textfile0.txt" offered by user "Carol" + When user "Brian" deletes file "/Shares/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/Shares/textfile0.txt" for user "Carol" should be "ownCloud test text file 0" + And the content of file "/Shares/textfile0.txt" for user "David" should be "ownCloud test text file 0" diff --git a/tests/acceptance/features/coreApiShareReshareToShares2/reShareDisabled.feature b/tests/acceptance/features/coreApiShareReshareToShares2/reShareDisabled.feature new file mode 100644 index 00000000000..1749f5f2b8e --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares2/reShareDisabled.feature @@ -0,0 +1,42 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: resharing can be disabled + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + + @smokeTest @skipOnOcis + Scenario Outline: resharing a file is not allowed when allow resharing has been disabled + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "share,update,read" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And parameter "shareapi_allow_resharing" of app "core" has been set to "no" + When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions "share,update,read" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" file "/Shares/textfile0.txt" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: ordinary sharing is allowed when allow resharing has been disabled + Given using OCS API version "" + And parameter "shareapi_allow_resharing" of app "core" has been set to "no" + When user "Alice" shares file "/textfile0.txt" with user "Brian" with permissions "share,update,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to accept pending share "/textfile0.txt" offered by user "Alice" + And as "Brian" file "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature b/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature new file mode 100644 index 00000000000..326aaf8e13e --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature @@ -0,0 +1,155 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: a subfolder of a received share can be reshared + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + @smokeTest @issue-ocis-2214 + Scenario Outline: User is allowed to reshare a sub-folder with the same permissions + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/TMP" + And user "Alice" has created folder "/TMP/SUB" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + When user "Brian" shares folder "/Shares/TMP/SUB" with user "Carol" with permissions "share,read" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to accept pending share "" offered by user "Brian" + And as "Carol" folder "/Shares/SUB" should exist + And as "Brian" folder "/Shares/TMP/SUB" should exist + Examples: + | ocs_api_version | ocs_status_code | pending_sub_share_path | + | 1 | 100 | /SUB | + | 2 | 200 | /SUB | + + + Scenario Outline: User is not allowed to reshare a sub-folder with more permissions + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/TMP" + And user "Alice" has created folder "/TMP/SUB" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions + And user "Brian" has accepted share "/TMP" offered by user "Alice" + When user "Brian" shares folder "/Shares/TMP/SUB" with user "Carol" with permissions using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/SUB" should not exist + And the sharing API should report to user "Carol" that no shares are in the pending state + And as "Brian" folder "/Shares/TMP/SUB" should exist + Examples: + | ocs_api_version | http_status_code | received_permissions | reshare_permissions | + # try to pass on more bits including reshare + | 1 | 200 | 17 | 19 | + | 2 | 404 | 17 | 19 | + | 1 | 200 | 17 | 21 | + | 2 | 404 | 17 | 21 | + | 1 | 200 | 17 | 23 | + | 2 | 404 | 17 | 23 | + | 1 | 200 | 17 | 31 | + | 2 | 404 | 17 | 31 | + | 1 | 200 | 19 | 23 | + | 2 | 404 | 19 | 23 | + | 1 | 200 | 19 | 31 | + | 2 | 404 | 19 | 31 | + # try to pass on more bits but not reshare + | 1 | 200 | 17 | 3 | + | 2 | 404 | 17 | 3 | + | 1 | 200 | 17 | 5 | + | 2 | 404 | 17 | 5 | + | 1 | 200 | 17 | 7 | + | 2 | 404 | 17 | 7 | + | 1 | 200 | 17 | 15 | + | 2 | 404 | 17 | 15 | + | 1 | 200 | 19 | 7 | + | 2 | 404 | 19 | 7 | + | 1 | 200 | 19 | 15 | + | 2 | 404 | 19 | 15 | + # try to pass on extra delete (including reshare) + | 1 | 200 | 17 | 25 | + | 2 | 404 | 17 | 25 | + | 1 | 200 | 19 | 27 | + | 2 | 404 | 19 | 27 | + | 1 | 200 | 23 | 31 | + | 2 | 404 | 23 | 31 | + # try to pass on extra delete (but not reshare) + | 1 | 200 | 17 | 9 | + | 2 | 404 | 17 | 9 | + | 1 | 200 | 19 | 11 | + | 2 | 404 | 19 | 11 | + | 1 | 200 | 23 | 15 | + | 2 | 404 | 23 | 15 | + + @issue-ocis-2214 + Scenario Outline: User is allowed to update reshare of a sub-folder with less permissions + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/TMP" + And user "Alice" has created folder "/TMP/SUB" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "/Shares/TMP/SUB" with user "Carol" with permissions "share,create,update,read" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | share,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as "Carol" folder "/Shares/SUB" should exist + But user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "/Shares/SUB/textfile.txt" + And as "Brian" folder "/Shares/TMP/SUB" should exist + And user "Brian" should be able to upload file "filesForUpload/textfile.txt" to "/Shares/TMP/SUB/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | pending_sub_share_path | + | 1 | 100 | /SUB | + | 2 | 200 | /SUB | + + @issue-ocis-2214 + Scenario Outline: User is allowed to update reshare of a sub-folder to the maximum allowed permissions + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/TMP" + And user "Alice" has created folder "/TMP/SUB" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "/Shares/TMP/SUB" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And as "Carol" folder "/Shares/SUB" should exist + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "/Shares/SUB/textfile.txt" + And as "Brian" folder "/Shares/TMP/SUB" should exist + And user "Brian" should be able to upload file "filesForUpload/textfile.txt" to "/Shares/TMP/SUB/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | pending_sub_share_path | + | 1 | 100 | /SUB | + | 2 | 200 | /SUB | + + @issue-ocis-2214 + Scenario Outline: User is not allowed to update reshare of a sub-folder with more permissions + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/TMP" + And user "Alice" has created folder "/TMP/SUB" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "/Shares/TMP/SUB" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | all | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/SUB" should exist + But user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "/Shares/SUB/textfile.txt" + And as "Brian" folder "/Shares/TMP/SUB" should exist + But user "Brian" should not be able to upload file "filesForUpload/textfile.txt" to "/Shares/TMP/SUB/textfile.txt" + Examples: + | ocs_api_version | http_status_code | pending_sub_share_path | + | 1 | 200 | /SUB | + | 2 | 404 | /SUB | diff --git a/tests/acceptance/features/coreApiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature b/tests/acceptance/features/coreApiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature new file mode 100644 index 00000000000..0e4b2410faf --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature @@ -0,0 +1,48 @@ +@api @files_sharing-app-required @issue-ocis-1289 @issue-ocis-reva-194 @issue-ocis-1328 @skipOnOcis +Feature: resharing a resource with an expiration date + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: User should not be able to re-share a folder to a group which he/she is not member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/PARENT" + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" shares folder "/Shares/PARENT" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/PARENT" should not exist + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | + + + Scenario Outline: User should not be able to re-share a file to a group which he/she is not member of when share with only member group is enabled + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" shares folder "/Shares/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "" + And as "Carol" folder "/Shares/textfile0.txt" should not exist + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 403 | 200 | + | 2 | 403 | 403 | diff --git a/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature b/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature new file mode 100644 index 00000000000..d8447d45319 --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature @@ -0,0 +1,160 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + + + Scenario Outline: Update of reshare can reduce permissions + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,create,update,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | share,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Update of reshare can increase permissions to the maximum allowed + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Do not allow update of reshare to exceed permissions + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | all | + Then the OCS status code should be "404" + And the HTTP status code should be "" + And user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: Update of user reshare by the original share owner can increase permissions up to the permissions of the top-level share + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Alice" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Update of user reshare by the original share owner can increase permissions to more than the permissions of the top-level share + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Alice" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Update of group reshare by the original share owner can increase permissions up to permissions of the top-level share + Given using OCS API version "" + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with group "grp1" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Alice" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Update of group reshare by the original share owner can increase permissions to more than the permissions of the top-level share + Given using OCS API version "" + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with group "grp1" with permissions "share,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Alice" updates the last share using the sharing API with + | permissions | share,create,update,read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: After losing share permissions user can still delete a previously reshared share + Given using OCS API version "" + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,create,update,read" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "Shares/TMP" with user "Carol" with permissions "share,create,update,read" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + And user "Alice" has updated the last share of "Alice" with + | permissions | create,update,read | + When user "Brian" deletes the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should not have any received shares + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature b/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature new file mode 100644 index 00000000000..5cbef9a893d --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDate.feature @@ -0,0 +1,445 @@ +@api @files_sharing-app-required @issue-ocis-1328 +Feature: resharing a resource with an expiration date + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + + + Scenario Outline: User should be able to set expiration while resharing a file with user + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +3 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | +3 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +3 days | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-reva-194 + Scenario Outline: User should be able to set expiration while resharing a file with group + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + | expireDate | +3 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | +3 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +3 days | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: resharing with user using the sharing API with expire days set and combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | expected-expire-date | ocs_status_code | + | 1 | yes | +30 days | 100 | + | 2 | yes | +30 days | 200 | + | 1 | no | | 100 | + | 2 | no | | 200 | + + @issue-ocis-reva-194 + Scenario Outline: resharing with group using the sharing API with expire days set and combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | expected-expire-date | ocs_status_code | + | 1 | yes | +30 days | 100 | + | 2 | yes | +30 days | 200 | + | 1 | no | | 100 | + | 2 | no | | 200 | + + + Scenario Outline: resharing with user using the sharing API without expire days set and with combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | expected-expire-date | ocs_status_code | + | 1 | yes | +7 days | 100 | + | 2 | yes | +7 days | 200 | + | 1 | no | | 100 | + | 2 | no | | 200 | + + @issue-ocis-reva-194 + Scenario Outline: resharing with group using the sharing API without expire days set and with combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | expected-expire-date | ocs_status_code | + | 1 | yes | +7 days | 100 | + | 2 | yes | +7 days | 200 | + | 1 | no | | 100 | + | 2 | no | | 200 | + + + Scenario Outline: resharing with user using the sharing API with expire days set and with combinations of default/enforce expire date enabled and specify expire date in share + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +20 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +20 days | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + @issue-ocis-reva-194 + Scenario Outline: resharing with group using the sharing API with expire days set and with combinations of default/enforce expire date enabled and specify expire date in share + Given using OCS API version "" + And parameter "shareapi_default_expire_date_group_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_group_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_group_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Carol" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | group | + | permissions | change | + | shareWith | grp1 | + | expireDate | +20 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +20 days | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + + Scenario Outline: Setting default expiry date and enforcement after the share is created + Given using OCS API version "" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has shared file "/Shares/textfile0.txt" with user "Carol" + And user "Carol" has accepted share "/textfile0.txt" offered by user "Brian" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "4" + When user "Brian" gets the info of the last share using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + @issue-ocis-reva-194 + Scenario Outline: resharing group share with user using the sharing API with default expire date set and with combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with group "grp1" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | expected-expire-date | ocs_status_code | + | 1 | yes | +30 days | 100 | + | 2 | yes | +30 days | 200 | + | 1 | no | | 100 | + | 2 | no | | 200 | + + @issue-ocis-reva-194 + Scenario Outline: resharing group share with user using the sharing API with default expire date set and specifying expiration on share and with combinations of default/enforce expire date enabled + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And group "grp1" has been created + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has been added to group "grp1" + And user "Alice" has shared file "/textfile0.txt" with group "grp1" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +20 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +20 days | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + + Scenario Outline: resharing using the sharing API with default expire date set but not enforced + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "no" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update,share" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Brian" should include + | expiration | | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + @skipOnOcV10 @issue-37013 + Scenario Outline: reshare extends the received expiry date up to the default by default + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Alice" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +20 | + Examples: + | ocs_api_version | default-expire-date | enforce-expire-date | ocs_status_code | + | 1 | yes | yes | 100 | + | 2 | yes | yes | 200 | + | 1 | yes | no | 100 | + | 2 | yes | no | 200 | + | 1 | no | no | 100 | + | 2 | no | no | 200 | + + @skipOnOcV10 @issue-37013 + Scenario Outline: reshare cannot extend the received expiry date further into the future + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "no" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +40 days | + #The action of changing the expiration date while resharing should be forbidden + Then the HTTP status code should be "403" + And the OCS status code should be "403" + And the information of the last share of user "Alice" should include + | expiration | +20 days | + Examples: + | ocs_api_version | default-expire-date | + | 1 | yes | + | 2 | yes | + | 1 | no | + | 2 | no | + + @skipOnOcV10 @issue-37013 + Scenario Outline: reshare cannot extend the received expiry date past the default when the default is enforced + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +40 days | + Then the HTTP status code should be "403" + And the OCS status code should be "403" + And the information of the last share of user "Alice" should include + | expiration | +20 days | + Examples: + | ocs_api_version | + | 1 | + | 2 | diff --git a/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDateOc10Issue37013.feature b/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDateOc10Issue37013.feature new file mode 100644 index 00000000000..bce4ee0a155 --- /dev/null +++ b/tests/acceptance/features/coreApiShareReshareToShares3/reShareWithExpiryDateOc10Issue37013.feature @@ -0,0 +1,110 @@ +@api @files_sharing-app-required @issue-ocis-1250 @notToImplementOnOCIS +Feature: resharing a resource with an expiration date + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + + @issue-37013 + Scenario Outline: reshare extends the received expiry date up to the default by default + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Alice" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | | + Examples: + | ocs_api_version | default-expire-date | enforce-expire-date | actual-expire-date | ocs_status_code | + | 1 | yes | yes | +30 days | 100 | + | 2 | yes | yes | +30 days | 200 | + | 1 | yes | no | | 100 | + | 2 | yes | no | | 200 | + | 1 | no | no | | 100 | + | 2 | no | no | | 200 | + + @issue-37013 + Scenario Outline: reshare can extend the received expiry date further into the future + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "no" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +40 days | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And user "Carol" should be able to accept pending share "/textfile0.txt" offered by user "Brian" + And the information of the last share of user "Alice" should include + | expiration | +20 days | + And the response when user "Carol" gets the info of the last share should include + | expiration | +40 days | + Examples: + | ocs_api_version | default-expire-date | ocs_status_code | + | 1 | yes | 100 | + | 2 | yes | 200 | + | 1 | no | 100 | + | 2 | no | 200 | + + @issue-37013 + Scenario Outline: reshare cannot extend the received expiry date past the default when the default is enforced + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + | expireDate | +20 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" creates a share using the sharing API with settings + | path | /Shares/textfile0.txt | + | shareType | user | + | permissions | change | + | shareWith | Carol | + | expireDate | +40 days | + Then the HTTP status code should be "" + And the OCS status code should be "404" + And the sharing API should report to user "Carol" that no shares are in the pending state + And the information of the last share of user "Alice" should include + | expiration | +20 days | + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | diff --git a/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature b/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature new file mode 100644 index 00000000000..15e1192eac9 --- /dev/null +++ b/tests/acceptance/features/coreApiShareUpdateToShares/updateShare.feature @@ -0,0 +1,441 @@ +@api @files_sharing-app-required +Feature: sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: Allow modification of reshare + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has created folder "/TMP" + And user "Alice" has shared folder "TMP" with user "Brian" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has shared folder "/Shares/TMP" with user "Carol" + And user "Carol" has accepted share "/TMP" offered by user "Brian" + When user "Brian" updates the last share using the sharing API with + | permissions | read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "/Shares/TMP/textfile.txt" + And user "Brian" should be able to upload file "filesForUpload/textfile.txt" to "/Shares/TMP/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1289 @notToImplementOnOCIS + Scenario Outline: keep group permissions in sync when the share is moved to another folder by the receiver and then the sharer updates the permissions + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has created folder "/FOLDER" + And user "Brian" has moved file "/Shares/textfile0.txt" to "/FOLDER/textfile0.txt" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | id | A_STRING | + | item_type | file | + | item_source | A_STRING | + | share_type | group | + | file_source | A_STRING | + | file_target | /Shares/textfile0.txt | + | permissions | read | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | mimetype | text/plain | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1289 + Scenario Outline: keep group permissions in sync when the share is renamed by the receiver and then the permissions are updated by sharer + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has moved file "/Shares/textfile0.txt" to "/Shares/textfile_new.txt" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with group "grp1" should include + | id | A_STRING | + | item_type | file | + | item_source | A_STRING | + | share_type | group | + | file_source | A_STRING | + | file_target | /Shares/textfile0.txt | + | permissions | read | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | mimetype | text/plain | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Cannot set permissions to zero + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with group "grp1" + When user "Alice" updates the last share using the sharing API with + | permissions | 0 | + Then the OCS status code should be "400" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + @issue-ocis-2173 + Scenario Outline: Cannot update a share of a file with a user to have only create and/or delete permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | | + Then the OCS status code should be "400" + And the HTTP status code should be "" + # Brian should still have at least read access to the shared file + And as "Brian" entry "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | http_status_code | permissions | + | 1 | 200 | create | + | 2 | 400 | create | + | 1 | 200 | delete | + | 2 | 400 | delete | + | 1 | 200 | create,delete | + | 2 | 400 | create,delete | + + @issue-ocis-2173 + Scenario Outline: Cannot update a share of a file with a group to have only create and/or delete permission + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | | + Then the OCS status code should be "400" + And the HTTP status code should be "" + # Brian in grp1 should still have at least read access to the shared file + And as "Brian" entry "/Shares/textfile0.txt" should exist + Examples: + | ocs_api_version | http_status_code | permissions | + | 1 | 200 | create | + | 2 | 400 | create | + | 1 | 200 | delete | + | 2 | 400 | delete | + | 1 | 200 | create,delete | + | 2 | 400 | create,delete | + + @skipOnFilesClassifier @issue-files-classifier-291 @toFixOnOCIS @issue-ocis-2201 + Scenario Outline: Share ownership change after moving a shared file outside of an outer share + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has created folder "/folder1" + And user "Alice" has created folder "/folder1/folder2" + And user "Brian" has created folder "/moved-out" + And user "Alice" has shared folder "/folder1" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/folder1" offered by user "Alice" + And user "Brian" has shared folder "/Shares/folder1/folder2" with user "Carol" with permissions "all" + And user "Carol" has accepted share "" offered by user "Brian" + When user "Brian" moves folder "/Shares/folder1/folder2" to "/moved-out/folder2" using the WebDAV API + Then the HTTP status code should be "201" + And the response when user "Brian" gets the info of the last share should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | user | + | file_source | A_STRING | + | file_target | /Shares/folder2 | + | permissions | all | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | mimetype | httpd/unix-directory | + And as "Alice" folder "/Shares/folder1/folder2" should not exist + And as "Carol" folder "/Shares/folder2" should exist + Examples: + | folder2_share_path | + | /folder2 | + + + Scenario Outline: Share ownership change after moving a shared file to another share + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + And user "Alice" has created folder "/Alice-folder" + And user "Alice" has created folder "/Alice-folder/folder2" + And user "Carol" has created folder "/Carol-folder" + And user "Alice" has shared folder "/Alice-folder" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/Alice-folder" offered by user "Alice" + And user "Carol" has shared folder "/Carol-folder" with user "Brian" with permissions "all" + And user "Brian" has accepted share "/Carol-folder" offered by user "Carol" + When user "Brian" moves folder "/Shares/Alice-folder/folder2" to "/Shares/Carol-folder/folder2" using the WebDAV API + Then the HTTP status code should be "201" + And the response when user "Carol" gets the info of the last share should include + | id | A_STRING | + | item_type | folder | + | item_source | A_STRING | + | share_type | user | + | file_source | A_STRING | + | file_target | | + | permissions | all | + | stime | A_NUMBER | + | storage | A_STRING | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | mimetype | httpd/unix-directory | + And as "Alice" folder "/Alice-folder/folder2" should not exist + And as "Carol" folder "/Carol-folder/folder2" should exist + @skipOnOcis + Examples: + | path | + | /Shares/Carol-folder | + + @skipOnOcV10 @issue-2442 + Examples: + | path | + | /Carol-folder | + + @toFixOnOCIS @toFixOnOcV10 @issue-ocis-reva-349 @issue-ocis-reva-350 @issue-ocis-reva-352 @issue-37653 + #after fixing all the issues merge this scenario with the one below + Scenario Outline: API responds with a full set of parameters when owner changes the permission of a share + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/Alice-folder" + And user "Alice" has shared folder "/Alice-folder" with user "Brian" with permissions "read" + When user "Alice" updates the last share using the sharing API with + | permissions | all | + Then the OCS status code should be "" + And the OCS status message should be "" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" sharing with user "Brian" should include + | id | A_STRING | + | share_type | user | + | uid_owner | %username% | + | displayname_owner | %displayname% | + | permissions | all | + | stime | A_NUMBER | + | parent | | + | expiration | | + | token | | + | uid_file_owner | %username% | + | displayname_file_owner | %displayname% | + | additional_info_owner | | + | additional_info_file_owner | | + | item_type | folder | + | item_source | A_STRING | + | path | /Alice-folder | + | mimetype | httpd/unix-directory | + | storage_id | A_STRING | + | storage | A_STRING | + | file_source | A_STRING | + | file_target | /Shares/Alice-folder | + | share_with | %username% | + | share_with_displayname | %displayname% | + | share_with_additional_info | | + | mail_send | 0 | + | attributes | | + And the fields of the last response should not include + | name | | + # | token | | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + + Scenario Outline: Increasing permissions is allowed for owner + Given using OCS API version "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Carol" has created folder "/FOLDER" + And user "Carol" has shared folder "/FOLDER" with group "grp1" + And user "Brian" has accepted share "/FOLDER" offered by user "Carol" + And user "Carol" has updated the last share with + | permissions | read | + When user "Carol" updates the last share using the sharing API with + | permissions | all | + Then the OCS status code should be "" + And the HTTP status code should be "200" + And user "Brian" should be able to upload file "filesForUpload/textfile.txt" to "/Shares/FOLDER/textfile.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: Forbid sharing with groups + Given using OCS API version "" + And group "grp1" has been created + And parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + When user "Alice" shares file "/textfile0.txt" with group "grp1" using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: Editing share permission of existing share is forbidden when sharing with groups is forbidden + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + When user "Alice" updates the last share using the sharing API with + | permissions | read, create | + Then the OCS status code should be "400" + And the HTTP status code should be "" + And the response when user "Alice" gets the info of the last share should include + | item_type | file | + | item_source | A_STRING | + | share_type | group | + | file_target | /Shares/textfile0.txt | + | permissions | read, update, share | + | mail_send | 0 | + | uid_owner | %username% | + | displayname_owner | %displayname% | + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 400 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: Deleting group share is allowed when sharing with groups is forbidden + Given using OCS API version "" + And group "grp1" has been created + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with group "grp1" + And parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + When user "Alice" deletes the last share using the sharing API + Then the OCS status code should be "" + And the HTTP status code should be "200" + When user "Alice" gets the info of the last share using the sharing API + Then the OCS status code should be "404" + And the HTTP status code should be "" + And the last response should be empty + Examples: + | ocs_api_version | ocs_status_code | http_status_code | + | 1 | 100 | 200 | + | 2 | 200 | 404 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: user can update the role in an existing share after the system maximum expiry date has been reduced + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +30 days | + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "5" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the fields of the last response to user "Alice" should include + | permissions | read | + | expiration | +30 days | + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @issue-ocis-1328 @skipOnOcis + Scenario Outline: user cannot concurrently update the role and date in an existing share after the system maximum expiry date has been reduced + Given using OCS API version "" + And parameter "shareapi_default_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_enforce_expire_date_user_share" of app "core" has been set to "yes" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "30" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has created a share with settings + | path | textfile0.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read,share | + | expireDate | +30 days | + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And parameter "shareapi_expire_after_n_days_user_share" of app "core" has been set to "10" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + | expireDate | +28 days | + Then the OCS status message should be "Cannot set expiration date more than 10 days in the future" + And the HTTP status code should be "" + And the OCS status code should be "404" + And the response when user "Alice" gets the info of the last share should include + | permissions | read, share | + | expiration | +30 days | + Examples: + | ocs_api_version | http_status_code | + | 1 | 200 | + | 2 | 404 | + + + Scenario Outline: Sharer deletes file uploaded with upload-only permission by sharee to a shared folder + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created a share with settings + | path | FOLDER | + | shareType | user | + | permissions | create | + | shareWith | Brian | + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Brian" has uploaded file with content "some content" to "/Shares/FOLDER/textFile.txt" + When user "Alice" deletes file "/FOLDER/textFile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" file "/Shares/FOLDER/textFile.txt" should not exist + And as "Alice" file "/textFile.txt" should not exist + Examples: + | dav-path | + | old | + | new | diff --git a/tests/acceptance/features/coreApiShareUpdateToShares/updateShareGroupAndUserWithSameName.feature b/tests/acceptance/features/coreApiShareUpdateToShares/updateShareGroupAndUserWithSameName.feature new file mode 100644 index 00000000000..090d8c2f3cc --- /dev/null +++ b/tests/acceptance/features/coreApiShareUpdateToShares/updateShareGroupAndUserWithSameName.feature @@ -0,0 +1,55 @@ +@api @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1328 +Feature: updating shares to users and groups that have the same name + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And group "Brian" has been created + And user "Carol" has been added to group "Brian" + And user "Alice" has created folder "/TMP" + And user "Alice" has uploaded file with content "Random data" to "/TMP/randomfile.txt" + + @skipOnLDAP + Scenario Outline: update permissions of a user share with a user and a group having the same name + Given using OCS API version "" + And user "Alice" has shared folder "/TMP" with group "Brian" + And user "Alice" has shared folder "/TMP" with user "Brian" + And user "Carol" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the content of file "/Shares/TMP/randomfile.txt" for user "Brian" should be "Random data" + And the content of file "/Shares/TMP/randomfile.txt" for user "Carol" should be "Random data" + And user "Carol" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile-by-Carol.txt" + But user "Brian" should not be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile-by-Brian.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | + + @skipOnLDAP + Scenario Outline: update permissions of a group share with a user and a group having the same name + Given using OCS API version "" + And user "Alice" has shared folder "/TMP" with user "Brian" + And user "Alice" has shared folder "/TMP" with group "Brian" + And user "Carol" has accepted share "/TMP" offered by user "Alice" + And user "Brian" has accepted share "/TMP" offered by user "Alice" + When user "Alice" updates the last share using the sharing API with + | permissions | read | + Then the HTTP status code should be "200" + And the OCS status code should be "" + And the content of file "/Shares/TMP/randomfile.txt" for user "Brian" should be "Random data" + And the content of file "/Shares/TMP/randomfile.txt" for user "Carol" should be "Random data" + And user "Brian" should be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile-by-Carol.txt" + But user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile-by-Brian.txt" + Examples: + | ocs_api_version | ocs_status_code | + | 1 | 100 | + | 2 | 200 | diff --git a/tests/acceptance/features/coreApiSharees/sharees.feature b/tests/acceptance/features/coreApiSharees/sharees.feature new file mode 100644 index 00000000000..e86128c1707 --- /dev/null +++ b/tests/acceptance/features/coreApiSharees/sharees.feature @@ -0,0 +1,726 @@ +@api @files_sharing-app-required @issue-ocis-reva-34 +Feature: sharees + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | sharee1 | + And group "ShareeGroup" has been created + And group "ShareeGroup2" has been created + And user "Alice" has been added to group "ShareeGroup2" + + @smokeTest + Scenario Outline: Search without exact match + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search without exact match not-exact casing + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sHaRee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with group members - denied + Given using OCS API version "" + And parameter "shareapi_only_share_with_group_members" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @skipOnLDAP + Scenario Outline: Search only with group members - allowed + Given using OCS API version "" + And parameter "shareapi_only_share_with_group_members" of app "core" has been set to "yes" + And user "Sharee1" has been added to group "ShareeGroup2" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with group members - no group as non-member + Given using OCS API version "" + And parameter "shareapi_only_share_with_group_members" of app "core" has been set to "yes" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When user "Sharee1" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with membership groups - denied + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When user "Sharee1" gets the sharees using the sharing API with parameters + | search | ShareeGroup | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with membership groups - denied but users match + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When user "Sharee1" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with membership groups - allowed + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | ShareeGroup | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search only with membership groups - allowed including users + Given using OCS API version "" + And parameter "shareapi_only_share_with_membership_groups" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | Sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search without exact match no iteration allowed + Given using OCS API version "" + And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" has been set to "no" + When user "Alice" gets the sharees using the sharing API with parameters + | search | Sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search with exact match no iteration allowed + Given using OCS API version "" + And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" has been set to "no" + When user "Alice" gets the sharees using the sharing API with parameters + | search | Sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Search with exact match group no iteration allowed + Given using OCS API version "" + And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" has been set to "no" + When user "Alice" gets the sharees using the sharing API with parameters + | search | ShareeGroup | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Try to search for users and groups when in a group that is excluded from sharing (could match both users and groups) + Given using OCS API version "" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["ShareeGroup2"]' + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Try to search for users and groups when in a group that is excluded from sharing (exact match to a user) + Given using OCS API version "" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["ShareeGroup2"]' + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Try to search for users and groups when in a group that is excluded from sharing (exact match to a group) + Given using OCS API version "" + And parameter "shareapi_exclude_groups" of app "core" has been set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" has been set to '["ShareeGroup2"]' + When user "Alice" gets the sharees using the sharing API with parameters + | search | ShareeGroup | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search with exact match + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | Sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search with exact match not-exact casing + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search with exact match not-exact casing group + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | shareegroup2 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search with "self" + Given using OCS API version "" + When user "Sharee1" gets the sharees using the sharing API with parameters + | search | Sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Federated sharee for files + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | test@localhost | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be + | test@localhost | 6 | test@localhost | + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Federated sharee for calendars not allowed + Given using OCS API version "" + When user "Alice" gets the sharees using the sharing API with parameters + | search | test@localhost | + | itemType | calendar | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Group sharees not returned when group sharing is disabled + Given using OCS API version "" + And parameter "shareapi_allow_group_sharing" of app "core" has been set to "no" + When user "Alice" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @skipOnLDAP + Scenario Outline: Enumerate only group members - only show partial results from member of groups + Given using OCS API version "" + And these users have been created with default attributes and without skeleton files: + | username | displayname | + | another | Another | + And user "Another" has been added to group "ShareeGroup2" + And parameter "shareapi_share_dialog_user_enumeration_group_members" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | anot | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Another | 0 | another | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Enumerate only group members - accept exact match from non-member groups + Given using OCS API version "" + And parameter "shareapi_share_dialog_user_enumeration_group_members" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | Sharee1 | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be + | Sharee One | 0 | sharee1 | + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Enumerate only group members - only show partial results from member groups + Given using OCS API version "" + And parameter "shareapi_share_dialog_user_enumeration_group_members" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | ShareeG | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @skipOnLDAP @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: Enumerate only group members - only accept exact group match from non-memberships + Given using OCS API version "" + And group "ShareeGroupNonMember" has been created + And parameter "shareapi_share_dialog_user_enumeration_group_members" of app "core" has been set to "yes" + When user "Alice" gets the sharees using the sharing API with parameters + | search | ShareeGroupNonMember | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be + | ShareeGroupNonMember | 1 | ShareeGroupNonMember | + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: Search without exact match such that the search string matches the user getting the sharees + Given user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be + | Sharee One | 0 | sharee1 | + | Sharee Two | 0 | sharee2 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: empty search for sharees when search min length is set to 0 + Given the administrator has updated system config key "user.search_min_length" with value "0" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | search | | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should include + | Alice Hansen | 0 | Alice | + | Sharee One | 0 | sharee1 | + | Sharee Two | 0 | sharee2 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should include + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: empty search for sharees when search min length is set to 2 + Given the administrator has updated system config key "user.search_min_length" with value "2" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | search | | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: search for sharees when search min length is set to 2 + Given the administrator has updated system config key "user.search_min_length" with value "2" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | search | sh | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should include + | Sharee One | 0 | sharee1 | + | Sharee Two | 0 | sharee2 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should include + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + + Scenario Outline: search for sharees with long name when search min length is set to 2 + Given the administrator has updated system config key "user.search_min_length" with value "2" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | search | sharee | + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should include + | Sharee One | 0 | sharee1 | + | Sharee Two | 0 | sharee2 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should include + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: search for sharees without search when min length is set to 0 + Given the administrator has updated system config key "user.search_min_length" with value "0" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should include + | Alice Hansen | 0 | Alice | + | Sharee One | 0 | sharee1 | + | Sharee Two | 0 | sharee2 | + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should include + | ShareeGroup | 1 | ShareeGroup | + | ShareeGroup2 | 1 | ShareeGroup2 | + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | + + @notToImplementOnOCIS @issue-ocis-1317 @issue-ocis-1328 + Scenario Outline: search for sharees without search when min length is set to 2 + Given the administrator has updated system config key "user.search_min_length" with value "2" + And user "sharee2" has been created with default attributes and without skeleton files + And using OCS API version "" + When user "sharee1" gets the sharees using the sharing API with parameters + | itemType | file | + Then the OCS status code should be "" + And the HTTP status code should be "" + And the "exact users" sharees returned should be empty + And the "users" sharees returned should be empty + And the "exact groups" sharees returned should be empty + And the "groups" sharees returned should be empty + And the "exact remotes" sharees returned should be empty + And the "remotes" sharees returned should be empty + Examples: + | ocs-api-version | ocs-status | http-status | + | 1 | 100 | 200 | + | 2 | 200 | 200 | diff --git a/tests/acceptance/features/coreApiTranslation/translation.feature b/tests/acceptance/features/coreApiTranslation/translation.feature new file mode 100644 index 00000000000..290a9de9379 --- /dev/null +++ b/tests/acceptance/features/coreApiTranslation/translation.feature @@ -0,0 +1,36 @@ +@api @skipOnLDAP @skipOnOcis +Feature: translate messages in api response to preferred language + As a user + I want response messages to be translated in preferred language + So that I can see and understand the response messages in my language + + Scenario Outline: user tries to get non existing share and uses some preferred language + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And using DAV path + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Brian" has shared file "textfile0.txt" with user "Carol" + When user "Alice" gets the info of the last share in language "" using the sharing API + Then the OCS status code should be "404" + And the OCS status message should be "Wrong share ID, share doesn't exist" in language "" + Examples: + | dav_version | language | + | old | de-DE | + | old | es-ES | + | old | zh-CN | + | old | fr-FR | + | new | de-DE | + | new | es-ES | + | new | zh-CN | + | new | fr-FR | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | language | + | spaces | de-DE | + | spaces | es-ES | + | spaces | zh-CN | + | spaces | fr-FR | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature b/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature new file mode 100644 index 00000000000..ae114b9e863 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature @@ -0,0 +1,328 @@ +@api @files_trashbin-app-required @issue-ocis-reva-52 +Feature: files and folders can be deleted from the trashbin + As a user + I want to delete files and folders from the trashbin + So that I can control my trashbin space and which files are kept in that space + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "to delete" to "/textfile0.txt" + And user "Alice" has uploaded file with content "to delete" to "/textfile1.txt" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file with content "to delete" to "/PARENT/parent.txt" + And user "Alice" has uploaded file with content "to delete" to "/PARENT/CHILD/child.txt" + + @smokeTest + Scenario Outline: Trashbin can be emptied + Given using DAV path + And user "Alice" has uploaded file with content "file with comma" to "sample,0.txt" + And user "Alice" has uploaded file with content "file with comma" to "sample,1.txt" + And user "Alice" has deleted file "" + And user "Alice" has deleted file "" + When user "Alice" empties the trashbin using the trashbin API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "" should not exist in the trashbin + And as "Alice" the file with original path "" should not exist in the trashbin + Examples: + | dav-path | filename1 | filename2 | + | new | textfile0.txt | textfile1.txt | + | new | sample,0.txt | sample,1.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | filename1 | filename2 | + | spaces | textfile0.txt | textfile1.txt | + | spaces | sample,0.txt | sample,1.txt | + + @smokeTest + Scenario Outline: delete a single file from the trashbin + Given using DAV path + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has deleted file "/textfile1.txt" + And user "Alice" has deleted file "/PARENT/parent.txt" + And user "Alice" has deleted file "/PARENT/CHILD/child.txt" + When user "Alice" deletes the file with original path "textfile1.txt" from the trashbin using the trashbin API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/textfile1.txt" should not exist in the trashbin + But as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @smokeTest + Scenario Outline: delete multiple files from the trashbin and make sure the correct ones are gone + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/textfile0.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/child.txt" + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has deleted file "/textfile1.txt" + And user "Alice" has deleted file "/PARENT/parent.txt" + And user "Alice" has deleted file "/PARENT/child.txt" + And user "Alice" has deleted file "/PARENT/textfile0.txt" + And user "Alice" has deleted file "/PARENT/CHILD/child.txt" + When user "Alice" deletes the file with original path "/PARENT/textfile0.txt" from the trashbin using the trashbin API + And user "Alice" deletes the file with original path "/PARENT/CHILD/child.txt" from the trashbin using the trashbin API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "/PARENT/textfile0.txt" should not exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should not exist in the trashbin + But as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: User tries to delete another user's trashbin + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has deleted file "/textfile1.txt" + And user "Alice" has deleted file "/PARENT/parent.txt" + And user "Alice" has deleted file "/PARENT/CHILD/child.txt" + When user "Brian" tries to delete the file with original path "textfile1.txt" from the trashbin of user "Alice" using the trashbin API + Then the HTTP status code should be "" + And as "Alice" the file with original path "/textfile1.txt" should exist in the trashbin + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should exist in the trashbin + @skipOnOcis + Examples: + | dav-path | status-code | + | new | 401 | + @skipOnOcV10 @personalSpace + Examples: + | dav-path | status-code | + | new | 404 | + | spaces | 404 | + + + Scenario Outline: User tries to delete trashbin file using invalid password + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has deleted file "/textfile1.txt" + And user "Alice" has deleted file "/PARENT/parent.txt" + And user "Alice" has deleted file "/PARENT/CHILD/child.txt" + When user "Brian" tries to delete the file with original path "textfile1.txt" from the trashbin of user "Alice" using the password "invalid" and the trashbin API + Then the HTTP status code should be "401" + And as "Alice" the file with original path "/textfile1.txt" should exist in the trashbin + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: User tries to delete trashbin file using no password + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + And user "Alice" has deleted file "/textfile1.txt" + And user "Alice" has deleted file "/PARENT/parent.txt" + And user "Alice" has deleted file "/PARENT/CHILD/child.txt" + When user "Brian" tries to delete the file with original path "textfile1.txt" from the trashbin of user "Alice" using the password "" and the trashbin API + Then the HTTP status code should be "401" + And as "Alice" the file with original path "/textfile1.txt" should exist in the trashbin + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: delete a folder that contains a file from the trashbin + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created folder "FOLDER/CHILD" + And user "Alice" has uploaded file with content "to delete" to "/FOLDER/parent.txt" + And user "Alice" has uploaded file with content "to delete" to "/FOLDER/CHILD/child.txt" + And user "Alice" has deleted folder "/PARENT" + And user "Alice" has deleted folder "/FOLDER" + When user "Alice" deletes the folder with original path "/PARENT" from the trashbin using the trashbin API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/PARENT/parent.txt" should not exist in the trashbin + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should not exist in the trashbin + And as "Alice" the folder with original path "/PARENT/CHILD/" should not exist in the trashbin + But as "Alice" the file with original path "/FOLDER/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/FOLDER/CHILD/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: delete a subfolder from a deleted folder from the trashbin + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created folder "FOLDER/CHILD" + And user "Alice" has uploaded file with content "to delete" to "/FOLDER/parent.txt" + And user "Alice" has uploaded file with content "to delete" to "/FOLDER/CHILD/child.txt" + And user "Alice" has deleted folder "/PARENT" + And user "Alice" has deleted folder "/FOLDER" + When user "Alice" deletes the folder with original path "/PARENT/CHILD" from the trashbin using the trashbin API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/PARENT/CHILD/child.txt" should not exist in the trashbin + And as "Alice" the folder with original path "/PARENT/CHILD/" should not exist in the trashbin + But as "Alice" the file with original path "/PARENT/parent.txt" should exist in the trashbin + But as "Alice" the file with original path "/FOLDER/parent.txt" should exist in the trashbin + And as "Alice" the file with original path "/FOLDER/CHILD/child.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: delete files with special characters from the trashbin + Given using DAV path + And user "Alice" has uploaded the following files with content "special character file" + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + And user "Alice" has deleted the following files + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + When user "Alice" deletes the following files with original path from the trashbin + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the files with following original paths should not exist in the trashbin + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: delete folders with special characters from the trashbin + Given using DAV path + And user "Alice" has created the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + And user "Alice" has deleted the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + When user "Alice" deletes the following files with original path from the trashbin + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the folders with following original paths should not exist in the trashbin + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: delete folders with dot in the name from the trashbin + Given using DAV path + And user "Alice" has created the following folders + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + And user "Alice" has deleted the following folders + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + When user "Alice" deletes the following files with original path from the trashbin + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the folders with following original paths should not exist in the trashbin + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature new file mode 100644 index 00000000000..df0ce24f161 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFolders.feature @@ -0,0 +1,479 @@ +@api @files_trashbin-app-required @issue-ocis-reva-52 +Feature: files and folders exist in the trashbin after being deleted + As a user + I want deleted files and folders to be available in the trashbin + So that I can recover data easily + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "to delete" to "/textfile0.txt" + + @smokeTest + Scenario Outline: deleting a file moves it to trashbin + Given using DAV path + When user "Alice" deletes file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/textfile0.txt" should exist in the trashbin + But as "Alice" file "/textfile0.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @smokeTest + Scenario Outline: deleting a folder moves it to trashbin + Given using DAV path + And user "Alice" has created folder "/tmp" + When user "Alice" deletes folder "/tmp" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/tmp" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: deleting a file in a folder moves it to the trashbin root + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" + When user "Alice" deletes file "/new-folder/new-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/new-folder/new-file.txt" should exist in the trashbin + And as "Alice" file "/new-file.txt" should exist in the trashbin + But as "Alice" file "/new-folder/new-file.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @files_sharing-app-required + Scenario Outline: deleting a file in a shared folder moves it to the trashbin root + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + When user "Alice" deletes file "/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Alice" file "/shared_file.txt" should exist in the trashbin + But as "Alice" file "/shared/shared_file.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @files_sharing-app-required + Scenario Outline: deleting a shared folder moves it to trashbin + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + When user "Alice" deletes folder "/shared" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the folder with original path "/shared" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @skipOnOcV10 @issue-23151 + # This scenario deletes many files as close together in time as the test can run. + # On a very slow system, the file deletes might all happen in different seconds. + # But on "reasonable" systems, some of the files will be deleted in the same second, + # thus testing the required behavior. + Scenario Outline: trashbin can store two files with the same name but different origins when the files are deleted close together in time + Given using DAV path + And user "Alice" has created folder "/folderA" + And user "Alice" has created folder "/folderB" + And user "Alice" has created folder "/folderC" + And user "Alice" has created folder "/folderD" + And user "Alice" has copied file "/textfile0.txt" to "/folderA/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderB/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderC/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderD/textfile0.txt" + When user "Alice" deletes these files without delays using the WebDAV API + | /textfile0.txt | + | /folderA/textfile0.txt | + | /folderB/textfile0.txt | + | /folderC/textfile0.txt | + | /folderD/textfile0.txt | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the folder with original path "/folderA/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/folderB/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/folderC/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/folderD/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + # Note: the underlying acceptance test code ensures that each delete step is separated by a least 1 second + Scenario Outline: trashbin can store two files with the same name but different origins when the deletes are separated by at least 1 second + Given using DAV path + And user "Alice" has created folder "/folderA" + And user "Alice" has created folder "/folderB" + And user "Alice" has copied file "/textfile0.txt" to "/folderA/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderB/textfile0.txt" + When user "Alice" deletes file "/folderA/textfile0.txt" using the WebDAV API + And user "Alice" deletes file "/folderB/textfile0.txt" using the WebDAV API + And user "Alice" deletes file "/textfile0.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the folder with original path "/folderA/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/folderB/textfile0.txt" should exist in the trashbin + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @local_storage @files_external-app-required @skipOnEncryptionType:user-keys @encryption-issue-42 @skip_on_objectstore + Scenario Outline: Deleting a folder into external storage moves it to the trashbin + Given using DAV path + And the administrator has invoked occ command "files:scan --all" + And user "Alice" has created folder "/local_storage/tmp" + And user "Alice" has moved file "/textfile0.txt" to "/local_storage/tmp/textfile0.txt" + When user "Alice" deletes folder "/local_storage/tmp" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the folder with original path "/local_storage/tmp" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @issue-ocis-3561 @skipOnLDAP @skip_on_objectstore + Scenario Outline: Listing other user's trashbin is prohibited + Given using DAV path + And user "testtrashbin100" has been created with default attributes and without skeleton files + And user "testtrashbin100" has uploaded file "filesForUpload/textfile.txt" to "/textfile1.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "testtrashbin100" has deleted file "/textfile1.txt" + When user "Brian" tries to list the trashbin content for user "testtrashbin100" + Then the HTTP status code should be "" + And the last webdav response should not contain the following elements + | path | user | + | textfile1.txt | testtrashbin100 | + @skipOnOcis + Examples: + | dav-path | status-code | + | new | 401 | + @skipOnOcV10 @personalSpace + Examples: + | dav-path | status-code | + | new | 404 | + | spaces | 404 | + + @issue-ocis-3561 @smokeTest @skipOnLDAP @skip_on_objectstore + Scenario Outline: Listing other user's trashbin is prohibited with multiple files on trashbin + Given using DAV path + And user "testtrashbin101" has been created with default attributes and without skeleton files + And user "testtrashbin101" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "testtrashbin101" has uploaded file "filesForUpload/textfile.txt" to "/textfile2.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "testtrashbin101" has deleted file "/textfile0.txt" + And user "testtrashbin101" has deleted file "/textfile2.txt" + When user "Brian" tries to list the trashbin content for user "testtrashbin101" + Then the HTTP status code should be "" + And the last webdav response should not contain the following elements + | path | user | + | textfile0.txt | testtrashbin101 | + | textfile2.txt | testtrashbin101 | + @skipOnOcis + Examples: + | dav-path | status-code | + | new | 401 | + @skipOnOcV10 @personalSpace + Examples: + | dav-path | status-code | + | new | 404 | + | spaces | 404 | + + @issue-ocis-3561 @skipOnLDAP @skip_on_objectstore @provisioning_api-app-required + Scenario Outline: Listing other user's trashbin is prohibited for newly recreated user with same name + Given using DAV path + And user "testtrashbin102" has been created with default attributes and without skeleton files + And user "testtrashbin102" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "testtrashbin102" has uploaded file "filesForUpload/textfile.txt" to "/textfile2.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "testtrashbin102" has deleted file "/textfile0.txt" + And user "testtrashbin102" has deleted file "/textfile2.txt" + And the administrator has deleted user "testtrashbin102" using the provisioning API + And user "testtrashbin102" has been created with default attributes and without skeleton files + And user "testtrashbin102" has uploaded file "filesForUpload/textfile.txt" to "/textfile3.txt" + And user "testtrashbin102" has deleted file "/textfile3.txt" + When user "Brian" tries to list the trashbin content for user "testtrashbin102" + Then the HTTP status code should be "" + And the last webdav response should not contain the following elements + | path | user | + | textfile0.txt | testtrashbin102 | + | textfile2.txt | testtrashbin102 | + | textfile3.txt | testtrashbin102 | + @skipOnOcis + Examples: + | dav-path | status-code | + | new | 401 | + @skipOnOcV10 @personalSpace + Examples: + | dav-path | status-code | + | new | 404 | + | spaces | 404 | + + @issue-ocis-3561 @skipOnLDAP @skip_on_objectstore + Scenario Outline: Listing other user's empty unused trashbin is prohibited + Given using DAV path + And user "testtrashbinempty" has been created with default attributes and without skeleton files + And user "testtrashbinempty" has uploaded file "filesForUpload/textfile.txt" to "/textfile1.txt" + When user "Alice" tries to list the trashbin content for user "testtrashbinempty" + Then the HTTP status code should be "" + @skipOnOcis + Examples: + | dav-path | status-code | + | new | 401 | + @skipOnOcV10 @personalSpace + Examples: + | dav-path | status-code | + | new | 404 | + | spaces | 404 | + + @issue-ocis-3561 @skipOnLDAP @skip_on_objectstore + Scenario Outline: Listing non-existent user's trashbin is prohibited + Given using DAV path + When user "Alice" tries to list the trashbin content for user "testtrashbinnotauser" + Then the HTTP status code should be "404" + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @smokeTest + Scenario Outline: Get trashbin content with wrong password + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" tries to list the trashbin content for user "Alice" using password "invalid" + Then the HTTP status code should be "401" + And the last webdav response should not contain the following elements + | path | user | + | /textfile0.txt | Alice | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @smokeTest + Scenario Outline: Get trashbin content without password + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" tries to list the trashbin content for user "Alice" using password "" + Then the HTTP status code should be "401" + And the last webdav response should not contain the following elements + | path | user | + | /textfile0.txt | Alice | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: user with unusual username deletes a file + Given using DAV path + And user "" has been created with default attributes and without skeleton files + And user "" has uploaded file with content "to delete" to "/textfile0.txt" + When user "" deletes file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "" file "/textfile0.txt" should exist in the trashbin + But as "" file "/textfile0.txt" should not exist + Examples: + | dav-path | username | + | new | dash-123 | + | new | null | + | new | nil | + | new | 123 | + | new | -123 | + | new | 0.0 | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | username | + | spaces | dash-123 | + | spaces | null | + | spaces | nil | + | spaces | 123 | + | spaces | -123 | + | spaces | 0.0 | + + + Scenario Outline: deleting a file with comma in the filename moves it to trashbin + Given using DAV path + And user "Alice" has uploaded file with content "file with comma in filename" to "sample,1.txt" + When user "Alice" deletes file "sample,1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "sample,1.txt" should exist in the trashbin + But as "Alice" file "sample,1.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: deleting a folder moves all its content to the trashbin + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/new-folder/new-file.txt" + When user "Alice" deletes folder "/new-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/new-folder/new-file.txt" should exist in the trashbin + And as "Alice" the folder with original path "/new-folder" should exist in the trashbin + And as "Alice" file "/new-folder/new-file.txt" should exist in the trashbin + But as "Alice" file "/new-folder/new-file.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @issue-ocis-541 + Scenario Outline: deleted file has appropriate deletion time information + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2018 04:18:13 GMT" using the WebDAV API + And user "Alice" has deleted file "file.txt" + When user "Alice" tries to list the trashbin content for user "Alice" + Then the HTTP status code should be "207" + And the deleted file "file.txt" should have the correct deletion mtime in the response + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @issue-ocis-1547 + Scenario Outline: deleting files with special characters moves it to trashbin + Given using DAV path + And user "Alice" has uploaded the following files with content "special character file" + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + When user "Alice" deletes the following files + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the following files should not exist + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + But as "Alice" the files with following original paths should exist in the trashbin + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + @issue-ocis-1547 + Scenario Outline: deleting folders with special characters moves it to trashbin + Given using DAV path + And user "Alice" has created the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + When user "Alice" deletes the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Then the HTTP status code of responses on all endpoints should be "204" + But as "Alice" the following folders should not exist + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + And as "Alice" the folders with following original paths should exist in the trashbin + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinFilesFoldersOc10Issue23151.feature b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFoldersOc10Issue23151.feature new file mode 100644 index 00000000000..578ed1c9756 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinFilesFoldersOc10Issue23151.feature @@ -0,0 +1,43 @@ +@api @files_trashbin-app-required @issue-ocis-reva-52 @notToImplementOnOCIS +Feature: files and folders exist in the trashbin after being deleted + As a user + I want deleted files and folders to be available in the trashbin + So that I can recover data easily + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "to delete" to "/textfile0.txt" + + # This scenario deletes many files as close together in time as the test can run. + # On a very slow system, the file deletes might all happen in different seconds. + # But on "reasonable" systems, some of the files will be deleted in the same second, + # thus testing the required behavior. + # Note: skipOnDbOracle because Oracle is slow and so "close together in time" does not really happen + @issue-23151 @skipOnDbOracle + Scenario Outline: trashbin can store two files with the same name but different origins when the files are deleted close together in time + Given using DAV path + And user "Alice" has created folder "/folderA" + And user "Alice" has created folder "/folderB" + And user "Alice" has created folder "/folderC" + And user "Alice" has created folder "/folderD" + And user "Alice" has copied file "/textfile0.txt" to "/folderA/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderB/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderC/textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/folderD/textfile0.txt" + When user "Alice" deletes these files without delays using the WebDAV API + | /textfile0.txt | + | /folderA/textfile0.txt | + | /folderB/textfile0.txt | + | /folderC/textfile0.txt | + | /folderD/textfile0.txt | + Then the HTTP status code of responses on all endpoints should be "204" + # When issue-23151 is fixed, uncomment these lines. They should pass reliably. + # These files may or may not exist in the trashbin +# Then as "Alice" the folder with original path "/folderA/textfile0.txt" should not exist in the trashbin +# And as "Alice" the folder with original path "/folderB/textfile0.txt" should not exist in the trashbin +# And as "Alice" the folder with original path "/folderC/textfile0.txt" should not exist in the trashbin +# And as "Alice" the folder with original path "/folderD/textfile0.txt" should not exist in the trashbin + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + Examples: + | dav-path | + | new | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToRoot.feature b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToRoot.feature new file mode 100644 index 00000000000..a078f13e318 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToRoot.feature @@ -0,0 +1,178 @@ +@api @files_trashbin-app-required @files_sharing-app-required @notToImplementOnOCIS +Feature: using trashbin together with sharing + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "to delete" to "/textfile0.txt" + + @smokeTest + Scenario Outline: deleting a received folder doesn't move it to trashbin + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has moved folder "/shared" to "/renamed_shared" + When user "Brian" deletes folder "/renamed_shared" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the folder with original path "/renamed_shared" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: deleting a file in a received folder moves it to the trashbin of both users + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has moved file "/shared" to "/renamed_shared" + When user "Brian" deletes file "/renamed_shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/renamed_shared/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: sharee deleting a file in a group-shared folder moves it to the trashbin of sharee and sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + When user "Brian" deletes file "/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Carol" the file with original path "/shared/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: sharer deleting a file in a group-shared folder moves it to the trashbin of sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + When user "Alice" deletes file "/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Brian" the file with original path "/shared/shared_file.txt" should not exist in the trashbin + And as "Carol" the file with original path "/shared/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: sharee deleting a folder in a group-shared folder moves it to the trashbin of sharee and sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + When user "Brian" deletes folder "/shared/sub" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin + And as "Carol" the file with original path "/shared/sub/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: sharer deleting a folder in a group-shared folder moves it to the trashbin of sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + When user "Alice" deletes folder "/shared/sub" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin + And as "Brian" the file with original path "/shared/sub/shared_file.txt" should not exist in the trashbin + And as "Carol" the file with original path "/shared/sub/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: deleting a file in a received folder when restored it comes back to the original path + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has uploaded file with content "to delete" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has moved file "/shared" to "/renamed_shared" + And user "Brian" has deleted file "/renamed_shared/shared_file.txt" + When user "Brian" restores the file with original path "/renamed_shared/shared_file.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Brian" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Brian" the file with original path "/renamed_shared/shared_file.txt" should not exist in the trashbin + And user "Brian" should see the following elements + | /renamed_shared/ | + | /renamed_shared/shared_file.txt | + And the content of file "/renamed_shared/shared_file.txt" for user "Brian" should be "to delete" + Examples: + | dav-path | + | new | + + + Scenario Outline: restoring a file to a read-only folder is not allowed + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "shareFolderParent" + And user "Brian" has shared folder "shareFolderParent" with user "Alice" with permissions "read" + And as "Alice" folder "/shareFolderParent" should exist + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" restores the file with original path "/textfile0.txt" to "/shareFolderParent/textfile0.txt" using the trashbin API + Then the HTTP status code should be "403" + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" file "/shareFolderParent/textfile0.txt" should not exist + And as "Brian" file "/shareFolderParent/textfile0.txt" should not exist + Examples: + | dav-path | + | new | + + + Scenario Outline: restoring a file to a read-only sub-folder is not allowed + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "shareFolderParent" + And user "Brian" has created folder "shareFolderParent/shareFolderChild" + And user "Brian" has shared folder "shareFolderParent" with user "Alice" with permissions "read" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt" + And as "Alice" folder "/shareFolderParent/shareFolderChild" should exist + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" restores the file with original path "/textfile0.txt" to "/shareFolderParent/shareFolderChild/textfile0.txt" using the trashbin API + Then the HTTP status code should be "403" + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" file "/shareFolderParent/shareFolderChild/textfile0.txt" should not exist + And as "Brian" file "/shareFolderParent/shareFolderChild/textfile0.txt" should not exist + Examples: + | dav-path | + | new | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature new file mode 100644 index 00000000000..104de3fc5b6 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature @@ -0,0 +1,236 @@ +@api @files_trashbin-app-required @files_sharing-app-required +Feature: using trashbin together with sharing + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file to delete" to "/textfile0.txt" + + @smokeTest + Scenario Outline: deleting a received folder doesn't move it to trashbin + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has moved folder "/Shares/shared" to "/Shares/renamed_shared" + When user "Brian" deletes folder "/Shares/renamed_shared" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the folder with original path "/Shares/renamed_shared" should not exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: deleting a file in a received folder moves it to trashbin of both users + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has moved file "/Shares/shared" to "/Shares/renamed_shared" + When user "Brian" deletes file "/Shares/renamed_shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/Shares/renamed_shared/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: sharee deleting a file in a group-shared folder moves it to the trashbin of sharee and sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + And user "Brian" has accepted share "/Shares/shared" offered by user "Alice" + And user "Carol" has accepted share "/Shares/shared" offered by user "Alice" + When user "Brian" deletes file "/Shares/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/Shares/shared/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Carol" the file with original path "/Shares/shared/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: sharer deleting a file in a group-shared folder moves it to the trashbin of sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + And user "Brian" has accepted share "/Shares/shared" offered by user "Alice" + And user "Carol" has accepted share "/Shares/shared" offered by user "Alice" + When user "Alice" deletes file "/shared/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/shared/shared_file.txt" should exist in the trashbin + And as "Brian" the file with original path "/Shares/shared/shared_file.txt" should not exist in the trashbin + And as "Carol" the file with original path "/Shares/shared/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: sharee deleting a folder in a group-shared folder moves it to the trashbin of sharee and sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + And user "Brian" has accepted share "/Shares/shared" offered by user "Alice" + And user "Carol" has accepted share "/Shares/shared" offered by user "Alice" + When user "Brian" deletes file "/Shares/shared/sub/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Brian" the file with original path "/Shares/shared/sub/shared_file.txt" should exist in the trashbin + And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin + And as "Carol" the file with original path "/Shares/sub/shared/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: sharer deleting a folder in a group-shared folder moves it to the trashbin of sharer only + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/shared" + And user "Alice" has created folder "/shared/sub" + And user "Alice" has moved file "/textfile0.txt" to "/shared/sub/shared_file.txt" + And user "Alice" has shared folder "/shared" with group "grp1" + And user "Brian" has accepted share "/Shares/shared" offered by user "Alice" + And user "Carol" has accepted share "/Shares/shared" offered by user "Alice" + When user "Alice" deletes file "/shared/sub/shared_file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin + And as "Brian" the file with original path "/Shares/shared/sub/shared_file.txt" should not exist in the trashbin + And as "Carol" the file with original path "/Shares/shared/sub/shared_file.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: deleting a file in a received folder when restored it comes back to the original path + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/shared" + And user "Alice" has moved file "/textfile0.txt" to "/shared/shared_file.txt" + And user "Alice" has shared folder "/shared" with user "Brian" + And user "Brian" has accepted share "/shared" offered by user "Alice" + And user "Brian" has moved folder "/Shares/shared" to "/Shares/renamed_shared" + And user "Brian" has deleted file "/Shares/renamed_shared/shared_file.txt" + When user "Brian" restores the file with original path "/Shares/renamed_shared/shared_file.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Brian" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Brian" the file with original path "/Shares/renamed_shared/shared_file.txt" should not exist in the trashbin + And user "Brian" should see the following elements + | /Shares/renamed_shared/ | + | /Shares/renamed_shared/shared_file.txt | + And the content of file "/Shares/renamed_shared/shared_file.txt" for user "Brian" should be "file to delete" + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: restoring a file to a read-only folder + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "shareFolderParent" + And user "Brian" has shared folder "shareFolderParent" with user "Alice" with permissions "read" + And user "Alice" has accepted share "/shareFolderParent" offered by user "Brian" + And as "Alice" folder "/Shares/shareFolderParent" should exist + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" restores the file with original path "/textfile0.txt" to "/Shares/shareFolderParent/textfile0.txt" using the trashbin API + Then the HTTP status code should be "403" + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" file "/Shares/shareFolderParent/textfile0.txt" should not exist + And as "Brian" file "/shareFolderParent/textfile0.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | + + + Scenario Outline: restoring a file to a read-only sub-folder + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "shareFolderParent" + And user "Brian" has created folder "shareFolderParent/shareFolderChild" + And user "Brian" has shared folder "shareFolderParent" with user "Alice" with permissions "read" + And user "Alice" has accepted share "/shareFolderParent" offered by user "Brian" + And as "Alice" folder "/Shares/shareFolderParent/shareFolderChild" should exist + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" restores the file with original path "/textfile0.txt" to "/Shares/shareFolderParent/shareFolderChild/textfile0.txt" using the trashbin API + Then the HTTP status code should be "403" + And as "Alice" the file with original path "/textfile0.txt" should exist in the trashbin + And as "Alice" file "/Shares/shareFolderParent/shareFolderChild/textfile0.txt" should not exist + And as "Brian" file "/shareFolderParent/shareFolderChild/textfile0.txt" should not exist + Examples: + | dav-path | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav-path | + | spaces | diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinSkip.feature b/tests/acceptance/features/coreApiTrashbin/trashbinSkip.feature new file mode 100644 index 00000000000..4fe1104b4fa --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbin/trashbinSkip.feature @@ -0,0 +1,323 @@ +@api @files_trashbin-app-required @notToImplementOnOCIS +Feature: files and folders can be deleted completely skipping the trashbin + As an admin + I want to configure some files to be deleted without the trashbin + So that I can control my trashbin space and which files are kept in that space + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "simple-folder" + And user "Alice" has created folder "lorem-folder" + + + Scenario Outline: Skip trashbin based on extensions + Given the administrator has set the following file extensions to be skipped from the trashbin + | extension | + | dat | + | php | + | go | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 1" to "sample.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "sample.dat" + And user "Alice" has uploaded file with content "sample delete file 3" to "sample.php" + And user "Alice" has uploaded file with content "sample delete file 4" to "sample.go" + And user "Alice" has uploaded file with content "sample delete file 5" to "sample.py" + When user "Alice" deletes the following files + | path | + | sample.txt | + | sample.dat | + | sample.php | + | sample.go | + | sample.py | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" file "sample.txt" should exist in the trashbin + And as "Alice" file "sample.py" should exist in the trashbin + But as "Alice" the file with original path "/sample.dat" should not exist in the trashbin + And as "Alice" the file with original path "/sample.php" should not exist in the trashbin + And as "Alice" the file with original path "/sample.go" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip trashbin based on extensions - file in a folder + Given the administrator has set the following file extensions to be skipped from the trashbin + | extension | + | dat | + | php | + | go | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 1" to "PARENT/sample.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/sample.dat" + And user "Alice" has uploaded file with content "sample delete file 3" to "PARENT/sample.php" + And user "Alice" has uploaded file with content "sample delete file 4" to "PARENT/sample.go" + And user "Alice" has uploaded file with content "sample delete file 5" to "PARENT/sample.py" + When user "Alice" deletes the following files + | path | + | PARENT/sample.txt | + | PARENT/sample.dat | + | PARENT/sample.php | + | PARENT/sample.go | + | PARENT/sample.py | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "/PARENT/sample.txt" should exist in the trashbin + And as "Alice" the file with original path "/PARENT/sample.py" should exist in the trashbin + But as "Alice" the file with original path "/PARENT/sample.dat" should not exist in the trashbin + And as "Alice" the file with original path "/PARENT/sample.php" should not exist in the trashbin + And as "Alice" the file with original path "/PARENT/sample.go" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip trashbin based on extensions - match is case-insensitive + Given the administrator has set the following file extensions to be skipped from the trashbin + | extension | + | dat | + | php | + | go | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 1" to "sample.TXT" + And user "Alice" has uploaded file with content "sample delete file 1" to "sample.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "sample.DAT" + And user "Alice" has uploaded file with content "sample delete file 2" to "sample.dat" + And user "Alice" has uploaded file with content "sample delete file 3" to "sample.PHP" + And user "Alice" has uploaded file with content "sample delete file 3" to "sample.php" + And user "Alice" has uploaded file with content "sample delete file 4" to "sample.GO" + And user "Alice" has uploaded file with content "sample delete file 4" to "sample.go" + And user "Alice" has uploaded file with content "sample delete file 5" to "sample.PY" + And user "Alice" has uploaded file with content "sample delete file 5" to "sample.py" + When user "Alice" deletes the following files + | path | + | sample.TXT | + | sample.txt | + | sample.DAT | + | sample.dat | + | sample.PHP | + | sample.php | + | sample.GO | + | sample.go | + | sample.PY | + | sample.py | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "/sample.TXT" should exist in the trashbin + And as "Alice" the file with original path "/sample.txt" should exist in the trashbin + And as "Alice" the file with original path "/sample.PY" should exist in the trashbin + And as "Alice" the file with original path "/sample.py" should exist in the trashbin + But as "Alice" the file with original path "/sample.DAT" should not exist in the trashbin + And as "Alice" the file with original path "/sample.dat" should not exist in the trashbin + And as "Alice" the file with original path "/sample.PHP" should not exist in the trashbin + And as "Alice" the file with original path "/sample.php" should not exist in the trashbin + And as "Alice" the file with original path "/sample.GO" should not exist in the trashbin + And as "Alice" the file with original path "/sample.go" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip trashbin based on extensions when deleting the parent folder - skip-by-extension rules should not be applied + Given the administrator has set the following file extensions to be skipped from the trashbin + | extension | + | dat | + | php | + | go | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 1" to "PARENT/sample.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/sample.dat" + And user "Alice" has uploaded file with content "sample delete file 3" to "PARENT/sample.php" + And user "Alice" has uploaded file with content "sample delete file 4" to "PARENT/sample.go" + And user "Alice" has uploaded file with content "sample delete file 5" to "PARENT/sample.py" + When user "Alice" deletes folder "PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "PARENT/sample.txt" should exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.py" should exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.dat" should exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.php" should exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.go" should exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip trashbin based on directory + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | PARENT | + | simple-folder | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 1" to "sample.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/sample.dat" + And user "Alice" has uploaded file with content "sample delete file 3" to "simple-folder/sample.php" + And user "Alice" has uploaded file with content "sample delete file 4" to "simple-folder/sample.go" + And user "Alice" has uploaded file with content "sample delete file 5" to "lorem-folder/sample.py" + When user "Alice" deletes the following files + | path | + | sample.txt | + | PARENT/sample.dat | + | simple-folder/sample.php | + | simple-folder/sample.go | + | lorem-folder/sample.py | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "sample.txt" should exist in the trashbin + And as "Alice" the file with original path "lorem-folder/sample.py" should exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.dat" should not exist in the trashbin + But as "Alice" the file with original path "simple-folder/sample.php" should not exist in the trashbin + And as "Alice" the file with original path "simple-folder/sample.go" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip trashbin based on directory should match only the root folder name + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | simple-folder | + And using DAV path + And user "Alice" has created folder "PARENT/simple-folder" + And user "Alice" has uploaded file with content "sample delete file 1" to "PARENT/p.txt" + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/simple-folder/sub.txt" + And user "Alice" has uploaded file with content "sample delete file 3" to "simple-folder/s.txt" + When user "Alice" deletes folder "PARENT" using the WebDAV API + And user "Alice" deletes folder "simple-folder" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "PARENT/p.txt" should exist in the trashbin + And as "Alice" the file with original path "PARENT/simple-folder/sub.txt" should exist in the trashbin + But as "Alice" the file with original path "simple-folder/s.txt" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Delete a file in a folder skipped from trashbin + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | PARENT | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/sample.dat" + When user "Alice" deletes file "PARENT/sample.dat" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "/PARENT" should not exist in the trashbin + And as "Alice" the file with original path "/PARENT/sample.dat" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Delete a file with same name as folder skipped from trashbin + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | skipFile | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 2" to "skipFile" + When user "Alice" deletes file "skipFile" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "skipFile" should exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Delete a file from a folder skipped from trashbin but different case + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | parent | + And using DAV path + And user "Alice" has uploaded file with content "sample delete file 2" to "PARENT/lorem.txt" + When user "Alice" deletes file "PARENT/lorem.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "PARENT/lorem.txt" should exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip file from trashbin based on size threshold + Given the administrator has set the trashbin skip size threshold to "10" + And using DAV path + And user "Alice" has uploaded file with content "sample" to "lorem.txt" + And user "Alice" has uploaded file with content "sample delete file" to "lorem.dat" + When user "Alice" deletes file "/lorem.txt" using the WebDAV API + And user "Alice" deletes file "/lorem.dat" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "lorem.txt" should exist in the trashbin + But as "Alice" the file with original path "lorem.dat" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip file from trashbin based on size threshold - file in a folder + Given the administrator has set the trashbin skip size threshold to "10" + And using DAV path + And user "Alice" has uploaded file with content "sample" to "PARENT/lorem.txt" + And user "Alice" has uploaded file with content "sample delete file" to "PARENT/lorem.dat" + When user "Alice" deletes file "PARENT/lorem.txt" using the WebDAV API + And user "Alice" deletes file "PARENT/lorem.dat" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "PARENT/lorem.txt" should exist in the trashbin + But as "Alice" the file with original path "PARENT/lorem.dat" should not exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Skip file from trashbin based on size threshold when deleting the parent folder - skip-by-size rules should not be applied + Given the administrator has set the trashbin skip size threshold to "10" + And using DAV path + And user "Alice" has uploaded file with content "sample" to "PARENT/lorem.txt" + And user "Alice" has uploaded file with content "sample delete file" to "PARENT/lorem.dat" + When user "Alice" deletes folder "PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" the file with original path "PARENT/lorem.txt" should exist in the trashbin + And as "Alice" the file with original path "PARENT/lorem.dat" should exist in the trashbin + Examples: + | dav-path | + | new | + + + Scenario Outline: Delete files when multiple skip trashbin rules are set + Given the administrator has set the following directories to be skipped from the trashbin + | directory | + | PARENT | + And the administrator has set the following file extensions to be skipped from the trashbin + | extension | + | dat | + And the administrator has set the trashbin skip size threshold to "20" + And using DAV path + # files that match none of the skip trashbin rules + And user "Alice" has uploaded file with content "sample" to "sample.txt" + And user "Alice" has uploaded file with content "sample" to "lorem-folder/sample.go" + # files that match just the "extension" skip trashbin rule + And user "Alice" has uploaded file with content "sample delete 1" to "sample.dat" + And user "Alice" has uploaded file with content "sample delete 3" to "lorem-folder/sample.dat" + # files that match just the "directory" skip trashbin rule + And user "Alice" has uploaded file with content "sample delete 2" to "PARENT/sample.txt" + # files that match just the "size threshold" skip trashbin rule + And user "Alice" has uploaded file with content "sample delete file long 2" to "simple-folder/sample.php" + # files that match 2 skip trashbin rules + And user "Alice" has uploaded file with content "sample delete file long 1" to "PARENT/sample.lis" + # files that match all 3 skip trashbin rules + And user "Alice" has uploaded file with content "sample delete file long 1" to "PARENT/sample.dat" + When user "Alice" deletes the following files + | path | + | sample.txt | + | lorem-folder/sample.go | + | sample.dat | + | lorem-folder/sample.dat | + | PARENT/sample.txt | + | simple-folder/sample.php | + | PARENT/sample.lis | + | PARENT/sample.dat | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the file with original path "sample.txt" should exist in the trashbin + And as "Alice" the file with original path "lorem-folder/sample.go" should exist in the trashbin + But as "Alice" the file with original path "sample.dat" should not exist in the trashbin + And as "Alice" the file with original path "lorem-folder/sample.dat" should not exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.txt" should not exist in the trashbin + And as "Alice" the file with original path "simple-folder/sample.php" should not exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.lis" should not exist in the trashbin + And as "Alice" the file with original path "PARENT/sample.dat" should not exist in the trashbin + Examples: + | dav-path | + | new | diff --git a/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature new file mode 100644 index 00000000000..950f2fdc537 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestore.feature @@ -0,0 +1,591 @@ +@api @files_trashbin-app-required @issue-ocis-reva-52 +Feature: Restore deleted files/folders + As a user + I would like to restore files/folders + So that I can recover accidentally deleted files/folders in ownCloud + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file to delete" to "/textfile0.txt" + + @smokeTest + Scenario Outline: A deleted file can be restored + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "parent text" to "/PARENT/parent.txt" + And user "Alice" has uploaded file with content "text file 1" to "/textfile1.txt" + And user "Alice" has deleted file "/textfile0.txt" + And as "Alice" file "/textfile0.txt" should exist in the trashbin + When user "Alice" restores the file with original path "/textfile0.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the file with original path "/textfile0.txt" should not exist in the trashbin + And the content of file "/textfile0.txt" for user "Alice" should be "file to delete" + And user "Alice" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /PARENT/parent.txt | + | /textfile0.txt | + | /textfile1.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A file deleted from a folder can be restored to the original folder + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" + And user "Alice" has deleted file "/new-folder/new-file.txt" + When user "Alice" restores the file with original path "/new-folder/new-file.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-folder/new-file.txt" should exist + And the content of file "/new-folder/new-file.txt" for user "Alice" should be "file to delete" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A file deleted from a folder is restored to the original folder if the original folder was deleted and restored + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" + And user "Alice" has deleted file "/new-folder/new-file.txt" + And user "Alice" has deleted folder "/new-folder" + When user "Alice" restores the folder with original path "/new-folder" using the trashbin API + And user "Alice" restores the file with original path "/new-folder/new-file.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-folder/new-file.txt" should exist + And the content of file "/new-folder/new-file.txt" for user "Alice" should be "file to delete" + Examples: + | dav-path | + | old | + | new | + + @skipOnFilesClassifier @issue-files-classifier-291 + Scenario Outline: a file is deleted and restored to a new destination + Given using DAV path + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/PARENT/CHILD" + And user "Alice" has uploaded file with content "to delete" to "" + And user "Alice" has deleted file "" + When user "Alice" restores the file with original path "" to "" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the file with original path "" should not exist in the trashbin + And as "Alice" file "" should exist + And as "Alice" file "" should not exist + And the content of file "" for user "Alice" should be "to delete" + Examples: + | dav-path | delete-path | restore-path | + | old | /PARENT/parent.txt | parent.txt | + | new | /PARENT/parent.txt | parent.txt | + | old | /PARENT/CHILD/child.txt | child.txt | + | new | /PARENT/CHILD/child.txt | child.txt | + | old | /textfile0.txt | PARENT/textfile0.txt | + | new | /textfile0.txt | PARENT/textfile0.txt | + + @skipOnOcV10 @issue-35974 + Scenario Outline: restoring a file to an already existing path overrides the file + Given user "Alice" has uploaded file with content "file to delete" to "/.hiddenfile0.txt" + And using DAV path + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "PARENT file content" to + And user "Alice" has deleted file + When user "Alice" restores the file with original path to using the trashbin API + Then the HTTP status code should be "204" + And as "Alice" file should exist + And the content of file for user "Alice" should be "file to delete" + Examples: + | dav-path | upload-path | delete-path | + | old | "/PARENT/textfile0.txt" | "/textfile0.txt" | + | new | "/PARENT/textfile0.txt" | "/textfile0.txt" | + | old | "/PARENT/.hiddenfile0.txt" | ".hiddenfile0.txt" | + | new | "/PARENT/.hiddenfile0.txt" | ".hiddenfile0.txt" | + + + Scenario Outline: A file deleted from a folder is restored to the original folder if the original folder was deleted and recreated + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/new-file.txt" + And user "Alice" has deleted file "/new-folder/new-file.txt" + And user "Alice" has deleted folder "/new-folder" + When user "Alice" creates folder "/new-folder" using the WebDAV API + And user "Alice" restores the file with original path "/new-folder/new-file.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the file with original path "/new-folder/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-folder/new-file.txt" should exist + And the content of file "/new-folder/new-file.txt" for user "Alice" should be "file to delete" + Examples: + | dav-path | + | old | + | new | + + @local_storage @files_external-app-required @skipOnEncryptionType:user-keys @encryption-issue-42 @skip_on_objectstore + Scenario Outline: Deleting a file into external storage moves it to the trashbin and can be restored + Given using DAV path + And the administrator has invoked occ command "files:scan --all" + And user "Alice" has created folder "/local_storage/tmp" + And user "Alice" has moved file "/textfile0.txt" to "/local_storage/tmp/textfile0.txt" + And user "Alice" has deleted file "/local_storage/tmp/textfile0.txt" + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in the trashbin + When user "Alice" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in the trashbin + And the content of file "/local_storage/tmp/textfile0.txt" for user "Alice" should be "file to delete" + And user "Alice" should see the following elements + | /local_storage/ | + | /local_storage/tmp/ | + | /local_storage/tmp/textfile0.txt | + Examples: + | dav-path | + | old | + | new | + + @local_storage @files_external-app-required @skipOnEncryptionType:user-keys @encryption-issue-42 @skip_on_objectstore + Scenario: Deleting an updated file into external storage moves it to the trashbin and can be restored + Given using old DAV path + And the administrator has invoked occ command "files:scan --all" + And user "Alice" has created folder "/local_storage/tmp" + And user "Alice" has moved file "/textfile0.txt" to "/local_storage/tmp/textfile0.txt" + And user "Alice" has uploaded chunk file "1" of "1" with "AA" to "/local_storage/tmp/textfile0.txt" + And user "Alice" has deleted file "/local_storage/tmp/textfile0.txt" + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in the trashbin + When user "Alice" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in the trashbin + And the content of file "/local_storage/tmp/textfile0.txt" for user "Alice" should be "AA" + + @local_storage @files_external-app-required @skipOnEncryptionType:user-keys @encryption-issue-42 @skip_on_objectstore @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Deleting an updated file into external storage moves it to the trashbin and can be restored with new chunking + Given using new DAV path + And the administrator has invoked occ command "files:scan --all" + And user "Alice" has created folder "/local_storage/tmp" + And user "Alice" has moved file "/textfile0.txt" to "/local_storage/tmp/textfile0.txt" + And user "Alice" has uploaded the following chunks to "/local_storage/tmp/textfile0.txt" with new chunking + | number | content | + | 1 | AA | + And user "Alice" has deleted file "/local_storage/tmp/textfile0.txt" + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in the trashbin + When user "Alice" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in the trashbin + And the content of file "/local_storage/tmp/textfile0.txt" for user "Alice" should be "AA" + + @smokeTest + Scenario Outline: A deleted file cannot be restored by a different user + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + When user "Brian" tries to restore the file with original path "/textfile0.txt" from the trashbin of user "Alice" using the trashbin API + Then the HTTP status code should be "" + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /textfile0.txt | + @skipOnOcis + Examples: + | dav-path | status-code | + | old | 401 | + | new | 401 | + @skipOnOcV10 + Examples: + | dav-path | status-code | + | old | 404 | + | new | 404 | + + @smokeTest + Scenario Outline: A deleted file cannot be restored with invalid password + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" tries to restore the file with original path "/textfile0.txt" from the trashbin of user "Alice" using the password "invalid" and the trashbin API + Then the HTTP status code should be "401" + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /textfile0.txt | + Examples: + | dav-path | + | old | + | new | + + @smokeTest + Scenario Outline: A deleted file cannot be restored without using a password + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has deleted file "/textfile0.txt" + When user "Alice" tries to restore the file with original path "/textfile0.txt" from the trashbin of user "Alice" using the password "" and the trashbin API + Then the HTTP status code should be "401" + And as "Alice" the folder with original path "/textfile0.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /textfile0.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: Files with strange names can be restored + Given using DAV path + And user "Alice" has uploaded file with content "file original content" to "" + And user "Alice" has deleted file "" + And user "Alice" restores the file with original path "" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "" should not exist in the trashbin + And as "Alice" file "" should exist + And the content of file "" for user "Alice" should be "file original content" + Examples: + | dav-path | file-to-upload | + | old | 😛 😜 🐱 🐭 ⌚️ ♀️ 🚴‍♂️ | + | new | 😛 😜 🐱 🐭 ⌚️ ♀️ 🚴‍♂️ | + | old | strängé नेपाली file | + | new | strängé नेपाली file | + | old | sample,1.txt | + | new | sample,1.txt | + + + Scenario Outline: A file deleted from a multi level sub-folder can be restored to the original folder + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has created folder "/new-folder/folder1/" + And user "Alice" has created folder "/new-folder/folder1/folder2/" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/folder1/folder2/new-file.txt" + And user "Alice" has deleted file "/new-folder/folder1/folder2/new-file.txt" + When user "Alice" restores the file with original path "/new-folder/folder1/folder2/new-file.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/folder1/folder2/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-folder/folder1/folder2/new-file.txt" should exist + And the content of file "/new-folder/folder1/folder2/new-file.txt" for user "Alice" should be "file to delete" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted multi level folder can be restored including the content + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has created folder "/new-folder/folder1/" + And user "Alice" has created folder "/new-folder/folder1/folder2/" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/folder1/folder2/new-file.txt" + And user "Alice" has deleted folder "/new-folder/" + When user "Alice" restores the folder with original path "/new-folder/" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/folder1/folder2/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-folder/folder1/folder2/new-file.txt" should exist + And the content of file "/new-folder/folder1/folder2/new-file.txt" for user "Alice" should be "file to delete" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A subfolder from a deleted multi level folder can be restored including the content + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has created folder "/new-folder/folder1" + And user "Alice" has created folder "/new-folder/folder1/folder2" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/folder1/folder2/new-file.txt" + And user "Alice" has deleted folder "/new-folder" + When user "Alice" restores the folder with original path "/new-folder/folder1" to "/folder1" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/folder1/folder2/new-file.txt" should not exist in the trashbin + And as "Alice" the folder with original path "/new-folder/folder1" should not exist in the trashbin + And as "Alice" file "/folder1/folder2/new-file.txt" should exist + And the content of file "/folder1/folder2/new-file.txt" for user "Alice" should be "file to delete" + But as "Alice" the folder with original path "/new-folder" should exist in the trashbin + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A file from a deleted multi level sub-folder can be restored + Given using DAV path + And user "Alice" has created folder "/new-folder" + And user "Alice" has created folder "/new-folder/folder1/" + And user "Alice" has created folder "/new-folder/folder1/folder2/" + And user "Alice" has moved file "/textfile0.txt" to "/new-folder/folder1/folder2/new-file.txt" + And user "Alice" has uploaded file with content "to delete" to "/new-folder/folder1/folder2/not-restored.txt" + And user "Alice" has deleted folder "/new-folder/" + When user "Alice" restores the file with original path "/new-folder/folder1/folder2/new-file.txt" to "new-file.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" the file with original path "/new-folder/folder1/folder2/new-file.txt" should not exist in the trashbin + And as "Alice" file "/new-file.txt" should exist + And the content of file "/new-file.txt" for user "Alice" should be "file to delete" + But as "Alice" the file with original path "/new-folder/folder1/folder2/not-restored.txt" should exist in the trashbin + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted hidden file can be restored + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + And user "Alice" has deleted the following files + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + When user "Alice" restores the following files with original path + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the files with following original paths should not exist in the trashbin + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + And as "Alice" the following files should exist + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: restoring files with special characters + Given using DAV path + And user "Alice" has uploaded the following files with content "special character file" + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + And user "Alice" has deleted the following files + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + When user "Alice" restores the following files with original path + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the files with following original paths should not exist in the trashbin + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + But as "Alice" the following files should exist + | path | + | qa&dev.txt | + | !@tester$^.txt | + | %file *?2.txt | + | # %ab ab?=ed.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: restoring folders with special characters + Given using DAV path + And user "Alice" has created the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + And user "Alice" has deleted the following folders + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + When user "Alice" restores the following folders with original path + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the folders with following original paths should not exist in the trashbin + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + But as "Alice" the following folders should exist + | path | + | qa&dev | + | !@tester$^ | + | %file *?2 | + | # %ab ab?=ed | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted file inside a nested folder can be restored to a different location + Given using DAV path + And user "Alice" has created folder "/parent_folder" + And user "Alice" has created folder "/parent_folder/sub" + And user "Alice" has uploaded file with content "parent text" to "/parent_folder/sub/parent.txt" + And user "Alice" has deleted folder "parent_folder" + When user "Alice" restores the folder with original path "/parent_folder/sub/parent.txt" to "parent.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the file with original path "/parent_folder/sub/parent.txt" should not exist in the trashbin + And the content of file "parent.txt" for user "Alice" should be "parent text" + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted file inside a nested folder cannot be restored to the original location if the location doesn't exist + Given using DAV path + And user "Alice" has created folder "/parent_folder" + And user "Alice" has created folder "/parent_folder/sub" + And user "Alice" has uploaded file with content "parent text" to "/parent_folder/sub/parent.txt" + And user "Alice" has deleted folder "parent_folder" + When user "Alice" restores the folder with original path "/parent_folder/sub/parent.txt" to "/parent_folder/sub/parent.txt" using the trashbin API + Then the HTTP status code should be "409" + And as "Alice" the file with original path "/parent_folder/sub/parent.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /parent_folder/ | + | /parent_folder/sub/ | + | /parent_folder/sub/parent.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted file inside a nested folder can be restored to the original location if the location exists + Given using DAV path + And user "Alice" has created folder "/parent_folder" + And user "Alice" has created folder "/parent_folder/sub" + And user "Alice" has uploaded file with content "parent text" to "/parent_folder/sub/parent.txt" + And user "Alice" has deleted folder "parent_folder" + And user "Alice" has created folder "/parent_folder" + And user "Alice" has created folder "/parent_folder/sub" + When user "Alice" restores the folder with original path "/parent_folder/sub/parent.txt" using the trashbin API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" the file with original path "/parent_folder/sub/parent.txt" should not exist in the trashbin + And the content of file "/parent_folder/sub/parent.txt" for user "Alice" should be "parent text" + And user "Alice" should see the following elements + | /parent_folder/ | + | /parent_folder/sub/ | + | /parent_folder/sub/parent.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted file inside a nested folder cannot be restored without the destination + Given using DAV path + And user "Alice" has created folder "/parent_folder" + And user "Alice" has created folder "/parent_folder/sub" + And user "Alice" has uploaded file with content "parent text" to "/parent_folder/sub/parent.txt" + And user "Alice" has deleted folder "parent_folder" + When user "Alice" restores the folder with original path "/parent_folder/sub/parent.txt" without specifying the destination using the trashbin API + Then the HTTP status code should be "400" + And as "Alice" the file with original path "/parent_folder/sub/parent.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /parent_folder/ | + | /parent_folder/sub/ | + | /parent_folder/sub/parent.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: A deleted file cannot be restored without the destination + Given using DAV path + And user "Alice" has uploaded file with content "parent text" to "/parent.txt" + And user "Alice" has deleted file "parent.txt" + When user "Alice" restores the folder with original path "parent.txt" without specifying the destination using the trashbin API + Then the HTTP status code should be "400" + And as "Alice" the file with original path "parent.txt" should exist in the trashbin + And user "Alice" should not see the following elements + | /parent.txt | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: restoring folders with dot in the name + Given using DAV path + And user "Alice" has created the following folders + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + And user "Alice" has deleted the following folders + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + When user "Alice" restores the following folders with original path + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the folders with following original paths should not exist in the trashbin + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + But as "Alice" the following folders should exist + | path | + | /fo. | + | /fo.1 | + | /fo...1.. | + | /... | + | /..fo | + | /fo.xyz | + | /fo.exe | + Examples: + | dav-path | + | old | + | new | diff --git a/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestoreOc10Issue35974.feature b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestoreOc10Issue35974.feature new file mode 100644 index 00000000000..3989f7d6e14 --- /dev/null +++ b/tests/acceptance/features/coreApiTrashbinRestore/trashbinRestoreOc10Issue35974.feature @@ -0,0 +1,34 @@ +@api @files_trashbin-app-required @issue-ocis-reva-52 @notToImplementOnOCIS +Feature: Restore deleted files/folders + As a user + I would like to restore files/folders + So that I can recover accidentally deleted files/folders in ownCloud + + @issue-35974 + Scenario Outline: restoring a file to an already existing path overrides the file + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file to delete" to "/textfile0.txt" + And user "Alice" has uploaded file with content "file to delete" to "/.hiddenfile0.txt" + And using DAV path + And user "Alice" has created folder "/PARENT" + And user "Alice" has uploaded file with content "PARENT file content" to + And user "Alice" has deleted file + When user "Alice" restores the file with original path to using the trashbin API + Then the HTTP status code should be "204" + # Sometimes is found in the trashbin. Should it? Or not? + # That seems to be what happens when the restore-overwrite happens properly, + # The original seems to be "deleted" and so goes to the trashbin + #And as "Alice" the file with original path should not exist in the trashbin + And as "Alice" file should exist + # sometimes the restore from trashbin does overwrite the existing file, but sometimes it does not. That is also surprising. + # the current observed behavior is that if the original ended up in the trashbin, + # then the new has the "file to delete" content. + # otherwise has its old content + And the content of file for user "Alice" if the file is also in the trashbin should be "file to delete" otherwise "PARENT file content" + #And the content of file for user "Alice" should be "file to delete" + Examples: + | dav-path | upload-path | delete-path | + | old | "/PARENT/textfile0.txt" | "/textfile0.txt" | + | new | "/PARENT/textfile0.txt" | "/textfile0.txt" | + | old | "/PARENT/.hiddenfile0.txt" | ".hiddenfile0.txt" | + | new | "/PARENT/.hiddenfile0.txt" | ".hiddenfile0.txt" | diff --git a/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature new file mode 100644 index 00000000000..dd04dbbaecc --- /dev/null +++ b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature @@ -0,0 +1,260 @@ +@api @files_versions-app-required @issue-ocis-reva-275 + +Feature: file versions remember the author of each version + + Background: + Given using OCS API version "2" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And the administrator has enabled the file version storage feature + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users + Given user "David" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Alice" has shared folder "/test" with user "Carol" with permissions "all" + And user "Alice" has shared folder "/test" with user "David" with permissions "all" + And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt" + And user "David" has uploaded file with content "uploaded content david" to "/test/textfile0.txt" + When user "Alice" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol" + And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Brian,Carol,David" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users for shared folder in the group + Given user "Alice" has created folder "/test" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has shared folder "/test" with group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt" + When user "Alice" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "2" + And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users for shared file in the group + Given group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with group "grp1" + And user "Brian" has uploaded file with content "uploaded content brian" to "/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/textfile0.txt" + When user "Alice" gets the number of versions of file "textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "2" + And the content of version index "1" of file "/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "2" of file "/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Brian,Carol" the authors of the versions of file "/textfile0.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + + @skip_on_objectstore @skipOnEncryption @issue-encryption-321 + Scenario: enable file versioning and check the history of changes from multiple users while moving file in/out of a subfolder + Given user "Alice" has created folder "/test" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has shared folder "/test" with group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt" + And user "Brian" has moved file "/test/textfile0.txt" to "/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian after moving file outside subfolder" to "/textfile0.txt" + And user "Brian" has moved file "/textfile0.txt" to "/test/textfile0.txt" + When user "Alice" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol" + And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users after renaming file by sharer + Given group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/exist.txt" + And user "Alice" has shared file "/exist.txt" with group "grp1" + And user "Brian" has uploaded file with content "uploaded content brian" to "/exist.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/exist.txt" + And user "Alice" has moved file "/exist.txt" to "/textfile0.txt" + When user "Alice" gets the number of versions of file "textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "2" + And the content of version index "1" of file "/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "2" of file "/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as user "Alice" the authors of the versions of file "/textfile0.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + And as users "Brian,Carol" the authors of the versions of file "/exist.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes in sharer after renaming file by sharee + Given group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/exist.txt" + And user "Alice" has shared file "/exist.txt" with group "grp1" + And user "Brian" has uploaded file with content "uploaded content brian" to "/exist.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/exist.txt" + And user "Brian" has moved file "/exist.txt" to "/textfile0.txt" + When user "Alice" gets the number of versions of file "exist.txt" + Then the HTTP status code should be "207" + And the number of versions should be "2" + And the content of version index "1" of file "/exist.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "2" of file "/exist.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Carol" the authors of the versions of file "/exist.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + And as user "Brian" the authors of the versions of file "/textfile0.txt" should be: + | index | author | + | 1 | Brian | + | 2 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users when reshared after unshared by sharer + Given user "Alice" has created folder "/test" + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has shared folder "/test" with group "grp1" + And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt" + And user "Alice" has deleted the last share + And user "Alice" has uploaded file with content "uploaded content alice after unshared folder" to "/test/textfile0.txt" + And user "Alice" has shared folder "/test" with group "grp1" + When user "Alice" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol" + And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users who have a matching folder/file + Given user "David" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/test" + And user "Brian" has uploaded file with content "duplicate brian" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "overwrite brian" to "/test/textfile0.txt" + And user "Carol" has created folder "/test" + And user "Carol" has uploaded file with content "duplicate carol" to "/test/textfile0.txt" + And user "Alice" has created folder "/test" + And user "Alice" has shared folder "/test" with user "Brian" with permissions "all" + And user "Alice" has shared folder "/test" with user "Carol" with permissions "all" + And user "Alice" has shared folder "/test" with user "David" with permissions "all" + And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt" + And user "Brian" has uploaded file with content "uploaded content brian" to "/test (2)/textfile0.txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/test (2)/textfile0.txt" + And user "David" has uploaded file with content "uploaded content david" to "/test/textfile0.txt" + When user "Alice" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol" + And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,David" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + And as users "Brian,Carol" the authors of the versions of file "/test (2)/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + When user "Brian" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + And the content of version index "1" of file "/test/textfile0.txt" for user "Brian" should be "duplicate brian" + And as user "Brian" the authors of the versions of file "/test/textfile0.txt" should be: + | index | author | + | 1 | Brian | + When user "Carol" gets the number of versions of file "/test/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "0" + + @skip_on_objectstore + Scenario: enable file versioning and check the history of changes from multiple users who have a matching file + Given user "David" has been created with default attributes and without skeleton files + And user "Brian" has uploaded file with content "duplicate brian" to "/textfile0.txt" + And user "Brian" has uploaded file with content "overwrite brian" to "/textfile0.txt" + And user "Carol" has uploaded file with content "duplicate carol" to "/textfile0.txt" + And user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + And user "Alice" has shared file "/textfile0.txt" with user "Carol" + And user "Alice" has shared file "/textfile0.txt" with user "David" + And user "Brian" has uploaded file with content "uploaded content brian" to "/textfile0 (2).txt" + And user "Carol" has uploaded file with content "uploaded content carol" to "/textfile0 (2).txt" + And user "David" has uploaded file with content "uploaded content david" to "/textfile0.txt" + When user "Alice" gets the number of versions of file "/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + And the content of version index "1" of file "/textfile0.txt" for user "Alice" should be "uploaded content carol" + And the content of version index "2" of file "/textfile0.txt" for user "Alice" should be "uploaded content brian" + And the content of version index "3" of file "/textfile0.txt" for user "Alice" should be "uploaded content alice" + And as users "Alice,David" the authors of the versions of file "/textfile0.txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + And as users "Brian,Carol" the authors of the versions of file "/textfile0 (2).txt" should be: + | index | author | + | 1 | Carol | + | 2 | Brian | + | 3 | Alice | + When user "Brian" gets the number of versions of file "/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + And the content of version index "1" of file "/textfile0.txt" for user "Brian" should be "duplicate brian" + And as user "Brian" the authors of the versions of file "/textfile0.txt" should be: + | index | author | + | 1 | Brian | + When user "Carol" gets the number of versions of file "/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "0" diff --git a/tests/acceptance/features/coreApiVersions/fileVersions.feature b/tests/acceptance/features/coreApiVersions/fileVersions.feature new file mode 100644 index 00000000000..e2f6a0ce6f2 --- /dev/null +++ b/tests/acceptance/features/coreApiVersions/fileVersions.feature @@ -0,0 +1,533 @@ +@api @files_versions-app-required @issue-ocis-reva-275 + +Feature: dav-versions + + Background: + Given using OCS API version "2" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Upload file and no version is available + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements + + @issue-ocis-reva-17 @issue-ocis-reva-56 + Scenario: Upload file and no version is available using various chunking methods (except new chunking) + When user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API + Then the HTTP status code should be "200" + And the version folder of file "/davtest.txt-olddav-regular" for user "Alice" should contain "0" elements + And the version folder of file "/davtest.txt-newdav-regular" for user "Alice" should contain "0" elements + And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "0" elements + + @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload file and no version is available using new chunking + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API + Then the HTTP status code should be "201" + And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements + + @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload file and no version is available using async upload + Given the administrator has enabled async operations + When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 3 chunks with new chunking and using the WebDAV API + Then the HTTP status code should be "202" + And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements + + @smokeTest + Scenario: Upload a file twice and versions are available + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 204" respectively + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + And the content length of file "/davtest.txt" with version index "1" for user "Alice" in versions folder should be "8" + + @issue-ocis-reva-17 @issue-ocis-reva-56 + Scenario: Upload a file twice and versions are available using various chunking methods (except new chunking) + When user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API + And user "Alice" uploads file "filesForUpload/davtest.txt" to filenames based on "/davtest.txt" with all mechanisms except new chunking using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "200" + And the version folder of file "/davtest.txt-olddav-regular" for user "Alice" should contain "1" element + And the version folder of file "/davtest.txt-newdav-regular" for user "Alice" should contain "1" element + And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "1" element + + @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload a file twice and versions are available using new chunking + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API + And user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API + Then the HTTP status code of responses on each endpoint should be "201, 204" respectively + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + + @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Scenario: Upload a file twice and versions are available using async upload + Given the administrator has enabled async operations + When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API + And user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 3 chunks with new chunking and using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "202" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + + @smokeTest + Scenario: Remove a file + Given user "Alice" has uploaded file "filesForUpload/davtest.txt" to "/davtest.txt" + And user "Alice" has uploaded file "filesForUpload/davtest.txt" to "/davtest.txt" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + And user "Alice" has deleted file "/davtest.txt" + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements + + @smokeTest + Scenario: Restore a file and check, if the content is now in the current file + Given user "Alice" has uploaded file with content "Test Content." to "/davtest.txt" + And user "Alice" has uploaded file with content "Content Test Updated." to "/davtest.txt" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + When user "Alice" restores version index "1" of file "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/davtest.txt" for user "Alice" should be "Test Content." + + @smokeTest @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-278 + Scenario: Restore a file back to bigger content and check, if the content is now in the current file + Given user "Alice" has uploaded file with content "Back To The Future." to "/davtest.txt" + And user "Alice" has uploaded file with content "Update Content." to "/davtest.txt" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + When user "Alice" restores version index "1" of file "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/davtest.txt" for user "Alice" should be "Back To The Future." + + @smokeTest @skipOnStorage:ceph @files_primary_s3-issue-161 @issue-ocis-reva-17 @issue-ocis-reva-56 + Scenario Outline: Uploading a chunked file does create the correct version that can be restored + Given using DAV path + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/textfile0.txt" in 2 chunks using the WebDAV API + And user "Alice" uploads file "filesForUpload/lorem.txt" to "/textfile0.txt" in 3 chunks using the WebDAV API +# HTTP status code is different for old (201) and new (204) WebDav API when uploading in chunks + Then the HTTP status code of responses on all endpoints should be "" + And the version folder of file "/textfile0.txt" for user "Alice" should contain "2" elements + When user "Alice" restores version index "1" of file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/textfile0.txt" for user "Alice" should be "Dav-Test" + Examples: + | dav-path | status-code | + | old | 201 | + @notToImplementOnOCIS @newChunking @issue-ocis-1321 + Examples: + | dav-path | status-code | + | new | 204 | + + @skipOnStorage:ceph @files_primary_s3-issue-161 @notToImplementOnOCIS @newChunking @issue-ocis-1321 @issue-ocis-reva-17 @issue-ocis-reva-56 @skipOnStorage:scality + Scenario: Uploading a file asynchronously does create the correct version that can be restored + Given the administrator has enabled async operations + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "textfile0.txt" in 2 chunks using the WebDAV API + And user "Alice" uploads file "filesForUpload/lorem.txt" asynchronously to "textfile0.txt" in 2 chunks using the WebDAV API + And user "Alice" restores version index "1" of file "/textfile0.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "202" + And the version folder of file "/textfile0.txt" for user "Alice" should contain "2" elements + And the content of file "/textfile0.txt" for user "Alice" should be "Dav-Test" + + @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-156 + Scenario: Restore a file and check, if the content and correct checksum is now in the current file + Given user "Alice" has uploaded file with content "AAAAABBBBBCCCCC" and checksum "MD5:45a72715acdd5019c5be30bdbb75233e" to "/davtest.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/davtest.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + When user "Alice" restores version index "1" of file "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/davtest.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And as user "Alice" the webdav checksum of "/davtest.txt" via propfind should match "SHA1:acfa6b1565f9710d4d497c6035d5c069bd35a8e8 MD5:45a72715acdd5019c5be30bdbb75233e ADLER32:1ecd03df" + + + Scenario: User cannot access meta folder of a file which is owned by somebody else + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "123" to "/davtest.txt" + And we save it into "FILEID" + When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta/<>" + Then the HTTP status code should be "400" or "404" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: User can access meta folder of a file which is owned by somebody else but shared with that user + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "123" to "/davtest.txt" + And user "Alice" has uploaded file with content "456789" to "/davtest.txt" + And we save it into "FILEID" + When user "Alice" creates a share using the sharing API with settings + | path | /davtest.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read | + Then the HTTP status code should be "200" + And the version folder of fileId "<>" for user "Brian" should contain "1" element + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharer of a file can see the old version information when the sharee changes the content of the file + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "First content" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + When user "Brian" has uploaded file with content "Second content" to "/sharefile.txt" + Then the HTTP status code should be "204" + And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharer of a file can restore the original content of a shared file after the file has been modified by the sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "First content" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Brian" has uploaded file with content "Second content" to "/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharer can restore a file inside a shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a file inside a shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a file inside a shared folder created by sharee and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer, when the folder has been moved by the sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + And user "Brian" has created folder "/received" + And user "Brian" has moved folder "/sharingfolder" to "/received/sharingfolder" + When user "Brian" restores version index "1" of file "/received/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is at the top level of the sharer) + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "old content" to "/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharefile.txt" + And user "Alice" has shared file "/sharefile.txt" with user "Brian" + And user "Brian" has created folder "/received" + And user "Brian" has moved file "/sharefile.txt" to "/received/sharefile.txt" + When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is inside a folder of the sharer) + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + And user "Alice" has shared file "/sharingfolder/sharefile.txt" with user "Brian" + And user "Brian" has created folder "/received" + And user "Brian" has moved file "/sharefile.txt" to "/received/sharefile.txt" + When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1321 @notToImplementOnOCIS + Scenario: sharer can restore a file inside a group shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with group "grp1" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + And user "Carol" has uploaded file with content "Third content" to "/sharingfolder/sharefile.txt" + When user "Alice" restores version index "2" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + And the content of file "/sharingfolder/sharefile.txt" for user "Carol" should be "First content" + + @files_sharing-app-required @notToImplementOnOCIS @issue-ocis-1238 + Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "" has uploaded file with content "test data 1" to "/testfile.txt" + And user "" has uploaded file with content "test data 2" to "/testfile.txt" + And user "" has uploaded file with content "test data 3" to "/testfile.txt" + When user "" moves file "/testfile.txt" to "/testshare/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testshare/testfile.txt" for user "Alice" should be "test data 3" + And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data 3" + And as "" file "/testfile.txt" should not exist + And the version folder of file "/testshare/testfile.txt" for user "Alice" should contain "2" elements + And the version folder of file "/testshare/testfile.txt" for user "Brian" should contain "2" elements + Examples: + | dav_version | mover | + | old | Alice | + | new | Alice | + | old | Brian | + | new | Brian | + + @files_sharing-app-required @notToImplementOnOCIS @issue-ocis-1238 + Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "test data 1" to "/testshare/testfile.txt" + And user "Brian" has uploaded file with content "test data 2" to "/testshare/testfile.txt" + And user "Brian" has uploaded file with content "test data 3" to "/testshare/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + When user "" moves file "/testshare/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testfile.txt" for user "" should be "test data 3" + And as "Alice" file "/testshare/testfile.txt" should not exist + And as "Brian" file "/testshare/testfile.txt" should not exist + And the version folder of file "/testfile.txt" for user "" should contain "2" elements + Examples: + | dav_version | mover | + | old | Alice | + | new | Alice | + | old | Brian | + | new | Brian | + + @files_sharing-app-required @issue-ocis-1238 @notToImplementOnOCIS + Scenario: Receiver tries to get file versions of unshared file from the sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" tries to get versions of file "textfile1.txt" from "Alice" + Then the HTTP status code should be "404" + And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + @skipOnStorage:ceph @files_primary_s3-issue-161 @files_sharing-app-required @notToImplementOnOCIS + Scenario: Receiver tries get file versions of shared file from the sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 3" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" tries to get versions of file "textfile0.txt" from "Alice" + Then the HTTP status code should be "207" + And the number of versions should be "3" + + + Scenario: User cannot access meta folder of a file which does not exist + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta/MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA==" + Then the HTTP status code should be "400" or "404" + + + Scenario Outline: User cannot access meta folder of a file with invalid fileid + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta//v" + Then the HTTP status code should be "400" or "404" + Examples: + | file-id | decoded-value | comment | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU3PThjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bdc4-0b0000009157=8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with = sign | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU3OGNjZDI3NTEtOTBhNC00MGYyLWI5ZjMtNjFlZGY4NDQyMWY0 | 1284d238-aa92-42ce-bdc4-0b00000091578ccd2751-90a4-40f2-b9f3-61edf84421f4 | no = sign | + | c29tZS1yYW5kb20tZmlsZUlkPWFub3RoZXItcmFuZG9tLWZpbGVJZA== | some-random-fileId=another-random-fileId | some random string | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkxzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bd�4-0b0000009156:8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with : and � sign | + + + Scenario: the version history is preserved when a file is renamed + Given user "Alice" has uploaded file with content "old content" to "/textfile.txt" + And user "Alice" has uploaded file with content "new content" to "/textfile.txt" + And user "Alice" has moved file "/textfile.txt" to "/renamedfile.txt" + When user "Alice" restores version index "1" of file "/renamedfile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/renamedfile.txt" for user "Alice" should be "old content" + + @issue-ocis-1238 + Scenario: User can access version number after moving a file + Given user "Alice" has created folder "testFolder" + And user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 3" to "textfile0.txt" + When user "Alice" moves file "textfile0.txt" to "/testFolder/textfile0.txt" using the WebDAV API + And user "Alice" gets the number of versions of file "/testFolder/textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "3" + + + Scenario: Original file has version number 0 + Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + When user "Alice" gets the number of versions of file "textfile0.txt" + Then the HTTP status code should be "207" + And the number of versions should be "0" + + @issue-ocis-1234 + Scenario: the number of etag elements in response changes according to version of the file + Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + When user "Alice" gets the number of versions of file "textfile0.txt" + Then the HTTP status code should be "207" + And the number of etag elements in the response should be "2" + And the number of versions should be "2" + + + Scenario: download old versions of a file + Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + When user "Alice" downloads the version of file "textfile0.txt" with the index "1" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "version 1" + When user "Alice" downloads the version of file "textfile0.txt" with the index "2" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "uploaded content" + + @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-463 + Scenario: download an old version of a restored file + Given user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And user "Alice" has restored version index "1" of file "textfile0.txt" + When user "Alice" downloads the version of file "textfile0.txt" with the index "1" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "version 2" + When user "Alice" downloads the version of file "textfile0.txt" with the index "2" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "uploaded content" + + + Scenario: User can retrieve meta information of a root folder + When user "Alice" retrieves the meta information of file "/" using the meta API + Then the HTTP status code should be "207" + And the single response should contain a property "oc:meta-path-for-user" with value "/" + + + Scenario: User can retrieve meta information of a file + Given user "Alice" has uploaded file with content "123" to "/davtest.txt" + When user "Alice" retrieves the meta information of file "/davtest.txt" using the meta API + Then the HTTP status code should be "207" + And the single response should contain a property "oc:meta-path-for-user" with value "/davtest.txt" + + + Scenario: User can retrieve meta information of a file inside folder + Given user "Alice" has created folder "testFolder" + And user "Alice" has uploaded file with content "123" to "/testFolder/davtest.txt" + When user "Alice" retrieves the meta information of file "/testFolder/davtest.txt" using the meta API + Then the HTTP status code should be "207" + And the single response should contain a property "oc:meta-path-for-user" with value "/testFolder/davtest.txt" + + + Scenario: User cannot retrieve meta information of a file which is owned by somebody else + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "123" to "/davtest.txt " + And we save it into "FILEID" + When user "Brian" retrieves the meta information of fileId "<>" using the meta API + Then the HTTP status code should be "404" + + + Scenario Outline: User cannot retrieve meta information of a file that does not exist + When user "Alice" retrieves the meta information of fileId "" using the meta API + Then the HTTP status code should be "400" or "404" + Examples: + | file-id | decoded-value | comment | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU3PThjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bdc4-0b0000009157=8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with = sign | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkYzQtMGIwMDAwMDA5MTU3OGNjZDI3NTEtOTBhNC00MGYyLWI5ZjMtNjFlZGY4NDQyMWY0 | 1284d238-aa92-42ce-bdc4-0b00000091578ccd2751-90a4-40f2-b9f3-61edf84421f4 | no = sign | + | c29tZS1yYW5kb20tZmlsZUlkPWFub3RoZXItcmFuZG9tLWZpbGVJZA== | some-random-fileId=another-random-fileId | some random string | + | MTI4NGQyMzgtYWE5Mi00MmNlLWJkxzQtMGIwMDAwMDA5MTU2OjhjY2QyNzUxLTkwYTQtNDBmMi1iOWYzLTYxZWRmODQ0MjFmNA== | 1284d238-aa92-42ce-bd�4-0b0000009156:8ccd2751-90a4-40f2-b9f3-61edf84421f4 | with : and � sign | + + + Scenario: File versions sets back after getting deleted and restored from trashbin + Given user "Alice" has uploaded file with content "Old Test Content." to "/davtest.txt" + And user "Alice" has uploaded file with content "New Test Content." to "/davtest.txt" + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + And user "Alice" has deleted file "/davtest.txt" + And as "Alice" file "/davtest.txt" should exist in the trashbin + When user "Alice" restores the file with original path "/davtest.txt" using the trashbin API + Then the HTTP status code should be "201" + And as "Alice" file "/davtest.txt" should exist + And the content of file "/davtest.txt" for user "Alice" should be "New Test Content." + And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element + When user "Alice" restores version index "1" of file "/davtest.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/davtest.txt" for user "Alice" should be "Old Test Content." diff --git a/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature b/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature new file mode 100644 index 00000000000..c53de028988 --- /dev/null +++ b/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature @@ -0,0 +1,324 @@ +@api @files_versions-app-required @issue-ocis-reva-275 + +Feature: dav-versions + + Background: + Given using OCS API version "2" + And using new DAV path + And the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Upload file and no version is available + When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API + Then the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements + + @files_sharing-app-required + Scenario: User can access meta folder of a file which is owned by somebody else but shared with that user + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "123" to "/davtest.txt" + And user "Alice" has uploaded file with content "456789" to "/davtest.txt" + And we save it into "FILEID" + And user "Alice" has created a share with settings + | path | /davtest.txt | + | shareType | user | + | shareWith | Brian | + | permissions | read | + When user "Brian" accepts share "/davtest.txt" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the version folder of fileId "<>" for user "Brian" should contain "1" element + + @files_sharing-app-required + Scenario: sharer of a file can see the old version information when the sharee changes the content of the file + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "First content" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + When user "Brian" uploads file with content "Second content" to "/Shares/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element + And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element + + @files_sharing-app-required + Scenario: sharer of a file can restore the original content of a shared file after the file has been modified by the sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "First content" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required + Scenario: sharer can restore a file inside a shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required + Scenario: sharee can restore a file inside a shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required + Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required + Scenario: sharee can restore a file inside a shared folder created by sharee and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + + @files_sharing-app-required + Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Brian" has uploaded file with content "old content" to "/Shares/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "new content" to "/Shares/sharingfolder/sharefile.txt" + When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required + Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer, when the folder has been moved by the sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with user "Brian" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + And user "Brian" has created folder "/received" + And user "Brian" has moved folder "/Shares/sharingfolder" to "/received/sharingfolder" + When user "Brian" restores version index "1" of file "/received/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharingfolder/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is at the top level of the sharer) + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "old content" to "/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharefile.txt" + And user "Alice" has shared file "/sharefile.txt" with user "Brian" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Brian" has created folder "/received" + And user "Brian" has moved file "/Shares/sharefile.txt" to "/received/sharefile.txt" + When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @notToImplementOnOCIS + Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is inside a folder of the sharer) + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt" + And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt" + And user "Alice" has shared file "/sharingfolder/sharefile.txt" with user "Brian" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + And user "Brian" has created folder "/received" + And user "Brian" has moved file "/Shares/sharefile.txt" to "/received/sharefile.txt" + When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content" + And the content of file "/received/sharefile.txt" for user "Brian" should be "old content" + + @files_sharing-app-required @issue-ocis-reva-34 + Scenario: sharer can restore a file inside a group shared folder modified by sharee + Given user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "/sharingfolder" + And user "Alice" has shared folder "/sharingfolder" with group "grp1" + And user "Brian" has accepted share "/sharingfolder" offered by user "Alice" + And user "Carol" has accepted share "/sharingfolder" offered by user "Alice" + And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt" + And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt" + And user "Carol" has uploaded file with content "Third content" to "/Shares/sharingfolder/sharefile.txt" + When user "Alice" restores version index "2" of file "/sharingfolder/sharefile.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content" + And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Carol" should be "First content" + + @files_sharing-app-required @issue-ocis-reva-386 + Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + And user "" has uploaded file with content "test data 1" to "/testfile.txt" + And user "" has uploaded file with content "test data 2" to "/testfile.txt" + And user "" has uploaded file with content "test data 3" to "/testfile.txt" + When user "" moves file "/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/Shares/testshare/testfile.txt" for user "Alice" should be "test data 3" + And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data 3" + And as "" file "/testfile.txt" should not exist + And the version folder of file "/Shares/testshare/testfile.txt" for user "Alice" should contain "2" elements + And the version folder of file "/testshare/testfile.txt" for user "Brian" should contain "2" elements + Examples: + | dav_version | mover | dst-folder | + | old | Alice | /Shares/testshare | + | new | Alice | /Shares/testshare | + | old | Brian | /testshare | + | new | Brian | /testshare | + + @files_sharing-app-required @issue-ocis-reva-386 + Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "test data 1" to "/testshare/testfile.txt" + And user "Brian" has uploaded file with content "test data 2" to "/testshare/testfile.txt" + And user "Brian" has uploaded file with content "test data 3" to "/testshare/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "" moves file "/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testfile.txt" for user "" should be "test data 3" + And as "Alice" file "/Shares/testshare/testfile.txt" should not exist + And as "Brian" file "/testshare/testfile.txt" should not exist + And the version folder of file "/testfile.txt" for user "" should contain "2" elements + + @notToImplementOnOCIS + Examples: + | dav_version | mover | src-folder | + | old | Alice | /Shares/testshare | + | new | Alice | /Shares/testshare | + + + Examples: + | dav_version | mover | src-folder | + | old | Brian | /testshare | + | new | Brian | /testshare | + + @files_sharing-app-required + Scenario: Receiver tries to get file versions of unshared file from the sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" tries to get versions of file "textfile1.txt" from "Alice" + Then the HTTP status code should be "404" + And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + @skipOnStorage:ceph @files_primary_s3-issue-161 @files_sharing-app-required + Scenario: Receiver tries get file versions of shared file from the sharer + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 3" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" tries to get versions of file "textfile0.txt" from "Alice" + Then the HTTP status code should be "207" + And the number of versions should be "3" + + + Scenario: Receiver tries get file versions of shared file before receiving it + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And we save it into "FILEID" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" tries to get versions of file "textfile0.txt" from "Alice" + Then the HTTP status code should be "404" + And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + + Scenario: sharer tries get file versions of shared file when the sharee changes the content of the file + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "First content" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice" + When user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt" + Then the HTTP status code should be "204" + And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element + And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element + + + Scenario: download old versions of a shared file as share receiver + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 1" to "textfile0.txt" + And user "Alice" has uploaded file with content "version 2" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "1" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "version 1" + When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "2" + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + And the downloaded content should be "uploaded content" diff --git a/tests/acceptance/features/coreApiWebdavDelete/deleteFile.feature b/tests/acceptance/features/coreApiWebdavDelete/deleteFile.feature new file mode 100644 index 00000000000..a7806543151 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavDelete/deleteFile.feature @@ -0,0 +1,138 @@ +@api +Feature: delete file + As a user + I want to be able to delete files + So that I can remove unwanted data + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: delete a file + Given using DAV path + And user "Alice" has uploaded file with content "to delete" to "/textfile0.txt" + When user "Alice" deletes file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/textfile0.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: delete a file when 2 files exist with different case + Given using DAV path + And user "Alice" has uploaded file with content "to delete" to "/textfile1.txt" + And user "Alice" has uploaded file with content "uploaded content" to "/TextFile1.txt" + When user "Alice" deletes file "/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/textfile1.txt" should not exist + And as "Alice" file "/TextFile1.txt" should exist + And the content of file "/TextFile1.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: delete file from folder with dots in the path + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content for file name with dots" to "/" + When user "Alice" deletes file "/" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/" should not exist + Examples: + | dav_version | folder_name | file_name | + | old | /upload. | abc. | + | old | /upload. | abc . | + | old | /upload.1 | abc.txt | + | old | /upload...1.. | abc...txt.. | + | old | /... | ... | + | old | /..upload | abc | + | old | /..upload | ..abc | + | new | /upload. | abc. | + | new | /upload. | abc . | + | new | /upload.1 | abc.txt | + | new | /upload...1.. | abc...txt.. | + | new | /... | ... | + | new | /..upload | abc | + | new | /..upload | ..abc | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /upload. | abc. | + | spaces | /upload...1.. | abc...txt.. | + | spaces | /upload.1 | abc.txt | + | spaces | /upload. | abc . | + | spaces | /... | ... | + | spaces | /..upload | abc | + | spaces | /..upload | ...abc | + + + Scenario Outline: delete a file with comma in the filename + Given using DAV path + And user "Alice" has uploaded file with content "file with comma in filename" to + When user "Alice" deletes file using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file should not exist + Examples: + | dav_version | filename | + | old | "sample,1.txt" | + | old | ",,,.txt" | + | old | ",,,.," | + | new | "sample,1.txt" | + | new | ",,,.txt" | + | new | ",,,.," | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | filename | + | spaces | "sample,1.txt" | + | spaces | ",,,.txt" | + | spaces | ",,,.," | + + + Scenario Outline: delete a hidden file + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + When user "Alice" deletes the following files + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Then the HTTP status code of responses on all endpoints should be "204" + And as "Alice" the following files should not exist + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario: delete a file of size zero byte + Given user "Alice" has uploaded file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" + When user "Alice" deletes file "/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/zerobyte.txt" should not exist \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavDelete/deleteFolder.feature b/tests/acceptance/features/coreApiWebdavDelete/deleteFolder.feature new file mode 100644 index 00000000000..65e088bf36f --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavDelete/deleteFolder.feature @@ -0,0 +1,142 @@ +@api +Feature: delete folder + As a user + I want to be able to delete folders + So that I can quickly remove unwanted data + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" creates folder "/PARENT" using the WebDAV API + + @smokeTest + Scenario Outline: delete a folder + Given using DAV path + When user "Alice" deletes folder "/PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/PARENT" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-269 + Scenario Outline: delete a folder when 2 folder exist with different case + Given using DAV path + And user "Alice" has created folder "/parent" + When user "Alice" deletes folder "/PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/PARENT" should not exist + But as "Alice" folder "/parent" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-269 + Scenario Outline: delete a sub-folder + Given using DAV path + And user "Alice" has created folder "/PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/PARENT/parent.txt" + When user "Alice" deletes folder "/PARENT/CHILD" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/PARENT/CHILD" should not exist + But as "Alice" folder "/PARENT" should exist + And as "Alice" file "/PARENT/parent.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: delete a folder when there is a default folder for received shares + Given using DAV path + And the administrator has set the default folder for received shares to "" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/Received" + And user "Brian" has created folder "/Top" + And user "Brian" has created folder "/Top/ReceivedShares" + And user "Alice" has shared folder "/PARENT" with user "Brian" + When user "Brian" deletes folder "" using the WebDAV API + Then the HTTP status code should be "403" + And as "Brian" folder "/PARENT" should exist + And user "Brian" should be able to delete folder "/Received" + And user "Brian" should be able to delete folder "/Top/ReceivedShares" + And user "Brian" should be able to delete folder "/Top" + Examples: + | dav_version | share_folder | + | old | ReceivedShares | + | old | /ReceivedShares | + | new | ReceivedShares | + | new | /ReceivedShares | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: delete a folder when there is a default folder for received shares that is a multi-level path + Given using DAV path + And the administrator has set the default folder for received shares to "/My/Received/Shares" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/M" + And user "Brian" has created folder "/Received" + And user "Brian" has created folder "/Received/Shares" + And user "Alice" has shared folder "/PARENT" with user "Brian" + When user "Brian" deletes folder "/My/Received/Shares" using the WebDAV API + Then the HTTP status code should be "403" + And user "Brian" should not be able to delete folder "/My/Received" + And user "Brian" should not be able to delete folder "/My" + And as "Brian" folder "/My/Received/Shares/PARENT" should exist + But user "Brian" should be able to delete folder "/M" + And user "Brian" should be able to delete folder "/Received/Shares" + And user "Brian" should be able to delete folder "/Received" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: deleting folder with dot in the name + Given using DAV path + And user "Alice" has created folder "" + When user "Alice" deletes folder "" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "" should not exist + Examples: + | dav_version | folder_name | + | old | /fo. | + | old | /fo.1 | + | old | /fo...1.. | + | old | /... | + | old | /..fo | + | old | /fo.xyz | + | old | /fo.exe | + | new | /fo. | + | new | /fo.1 | + | new | /fo...1.. | + | new | /... | + | new | /..fo | + | new | /fo.xyz | + | new | /fo.exe | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | + | spaces | /fo. | + | spaces | /fo.1 | + | spaces | /fo...1.. | + | spaces | /... | + | spaces | /..fo | + | spaces | /fo.xyz | + | spaces | /fo.exe | diff --git a/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature b/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature new file mode 100644 index 00000000000..cb14fe4d4c4 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavDelete/deleteFolderContents.feature @@ -0,0 +1,39 @@ +@api +Feature: delete folder contents + As a user + I want to be able to delete all files and folders in a folder + So that I can quickly remove unwanted data + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Removing everything of a folder + Given using DAV path + And user "Alice" has created folder "/PARENT/" + And user "Alice" has created folder "/FOLDER/" + And user "Alice" has created folder "/FOLDER/SUBFOLDER" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/textfile0.txt" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/textfile1.txt" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/FOLDER/fileToDelete.txt" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/FOLDER/SUBFOLDER/textfile0.txt" + When user "Alice" deletes everything from folder "/FOLDER/" using the WebDAV API + Then the HTTP status code should be "204" + And user "Alice" should see the following elements + | /FOLDER/ | + | /PARENT/ | + | /textfile0.txt | + | /textfile1.txt | + And user "Alice" should not see the following elements + | /FOLDER/SUBFOLDER/ | + | /FOLDER/fileToDelete.txt | + | /FOLDER/SUBFOLDER/testfile0.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature new file mode 100644 index 00000000000..8f37b93a4de --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature @@ -0,0 +1,262 @@ +@api +Feature: propagation of etags when deleting a file or folder + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And user "Alice" has created folder "/upload" + + + Scenario Outline: deleting a file changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" deletes file "/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: deleting a folder changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" deletes folder "/upload/sub/toDelete" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: deleting a folder with content changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/toDelete/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" deletes folder "/upload/sub/toDelete" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: as share receiver deleting a file changes the etags of all parents for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/sub" + When user "Brian" deletes file "/Shares/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/sub | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as sharer deleting a file changes the etags of all parents for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/sub" + When user "Alice" deletes file "/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/sub | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as share receiver deleting a folder changes the etags of all parents for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/sub" + When user "Brian" deletes folder "/Shares/upload/sub/toDelete" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/sub | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as sharer deleting a folder changes the etags of all parents for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/sub" + When user "Alice" deletes folder "/upload/sub/toDelete" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/sub | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: deleting a file in a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | change | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When the public deletes file "file.txt" from the last public link share using the new public WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: deleting a folder in a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | change | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When the public deletes folder "sub" from the last public link share using the new public WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature new file mode 100644 index 00000000000..bd9b3a9f48c --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature @@ -0,0 +1,409 @@ +@api +Feature: propagation of etags when moving files or folders + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: renaming a file inside a folder changes its etag + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When user "Alice" moves file "/upload/file.txt" to "/upload/renamed.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: moving a file from one folder to an other changes the etags of both folders + Given using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/dst" + And user "Alice" has uploaded file with content "uploaded content" to "/src/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + When user "Alice" moves file "/src/file.txt" to "/dst/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: moving a file into a subfolder changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" moves file "/upload/file.txt" to "/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: renaming a folder inside a folder changes its etag + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/src" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When user "Alice" moves folder "/upload/src" to "/upload/dst" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: moving a folder from one folder to an other changes the etags of both folders + Given using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/src/folder" + And user "Alice" has created folder "/dst" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + When user "Alice" moves folder "/src/folder" to "/dst/folder" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: moving a folder into a subfolder changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/folder" + And user "Alice" has created folder "/upload/sub" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" moves folder "/upload/folder" to "/upload/sub/folder" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: as share receiver renaming a file inside a folder changes its etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Brian" moves file "/Shares/upload/file.txt" to "/Shares/upload/renamed.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as sharer renaming a file inside a folder changes its etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Alice" moves file "/upload/file.txt" to "/upload/renamed.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as sharer moving a file from one folder to an other changes the etags of both folders for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/dst" + And user "Alice" has uploaded file with content "uploaded content" to "/src/file.txt" + And user "Alice" has shared folder "/src" with user "Brian" + And user "Brian" has accepted share "/src" offered by user "Alice" + And user "Alice" has shared folder "/dst" with user "Brian" + And user "Brian" has accepted share "/dst" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/src" + And user "Brian" has stored etag of element "/Shares/dst" + When user "Alice" moves file "/src/file.txt" to "/dst/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/src | + | Brian | /Shares/dst | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as share receiver moving a file from one folder to an other changes the etags of both folders for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/dst" + And user "Alice" has uploaded file with content "uploaded content" to "/src/file.txt" + And user "Alice" has shared folder "/src" with user "Brian" + And user "Brian" has accepted share "/src" offered by user "Alice" + And user "Alice" has shared folder "/dst" with user "Brian" + And user "Brian" has accepted share "/dst" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/src" + And user "Brian" has stored etag of element "/Shares/dst" + When user "Brian" moves file "/Shares/src/file.txt" to "/Shares/dst/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/src | + | Brian | /Shares/dst | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as sharer moving a folder from one folder to an other changes the etags of both folders for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/dst" + And user "Alice" has created folder "/src/toMove" + And user "Alice" has shared folder "/src" with user "Brian" + And user "Brian" has accepted share "/src" offered by user "Alice" + And user "Alice" has shared folder "/dst" with user "Brian" + And user "Brian" has accepted share "/dst" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/src" + And user "Brian" has stored etag of element "/Shares/dst" + When user "Alice" moves folder "/src/toMove" to "/dst/toMove" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/src | + | Brian | /Shares/dst | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as share receiver moving a folder from one folder to an other changes the etags of both folders for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has created folder "/dst" + And user "Alice" has created folder "/src/toMove" + And user "Alice" has shared folder "/src" with user "Brian" + And user "Brian" has accepted share "/src" offered by user "Alice" + And user "Alice" has shared folder "/dst" with user "Brian" + And user "Brian" has accepted share "/dst" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/src" + And user "Brian" has stored etag of element "/Shares/dst" + When user "Brian" moves folder "/Shares/src/toMove" to "/Shares/dst/toMove" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /src | + | Alice | /dst | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/src | + | Brian | /Shares/dst | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: renaming a file in a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | change | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When the public renames file "file.txt" to "renamed.txt" from the last public link share using the new public WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: renaming a folder in a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | change | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When the public renames folder "sub" to "renamed" from the last public link share using the new public WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature new file mode 100644 index 00000000000..0d2be53815a --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature @@ -0,0 +1,230 @@ +@api +Feature: propagation of etags when copying files or folders + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @issue-product-280 + Scenario Outline: copying a file does not change its etag + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/file.txt" + And user "Alice" has stored etag of element "/file.txt" on path "/renamedFile.txt" + When user "Alice" copies file "/file.txt" to "/renamedFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should not have changed: + | user | path | + | Alice | /file.txt | + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /renamedFile.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: copying a file inside a folder changes its etag + Given using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + And user "Alice" has stored etag of element "/file.txt" + And user "Alice" has stored etag of element "/folder" + And user "Alice" has stored etag of element "/file.txt" on path "/folder/renamedFile.txt" + When user "Alice" copies file "/file.txt" to "/folder/renamedFile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should not have changed: + | user | path | + | Alice | /file.txt | + And these etags should have changed: + | user | path | + | Alice | /folder/renamedFile.txt | + | Alice | /folder | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: copying a file from one folder to an other changes the etags of destination + Given using DAV path + And user "Alice" has created folder "/src" + And user "Alice" has uploaded file with content "uploaded content" to "/src/file.txt" + And user "Alice" has created folder "/dst" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/src" + And user "Alice" has stored etag of element "/dst" + When user "Alice" copies folder "/src/file.txt" to "/dst/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /dst | + And these etags should not have changed: + | user | path | + | Alice | /src | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: copying a file into a subfolder changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/file.txt" + And user "Alice" has stored etag of element "/upload/file.txt" on path "/upload/sub/file.txt" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" copies file "/upload/file.txt" to "/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + | Alice | /upload/sub/file.txt | + And these etags should not have changed: + | user | path | + | Alice | /upload/file.txt | + @issue-ocis-4091 + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: copying a file inside a publicly shared folder by public changes etag for the sharer + Given using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | change | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/file.txt" + And user "Alice" has stored etag of element "/upload/file.txt" on path "/upload/renamedFile.txt" + When the public copies file "file.txt" to "/renamedFile.txt" using the new public WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/renamedFile.txt | + And these etags should not have changed: + | user | path | + | Alice | /upload/file.txt | + @issue-ocis-4091 + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: as share receiver copying a file inside a folder changes its etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/file.txt" + And user "Alice" has stored etag of element "/upload/file.txt" on path "/upload/renamed.txt" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/file.txt" + And user "Brian" has stored etag of element "/Shares/upload/file.txt" on path "/Shares/upload/renamed.txt" + When user "Brian" copies file "/Shares/upload/file.txt" to "/Shares/upload/renamed.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/renamed.txt | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/renamed.txt | + And these etags should not have changed: + | user | path | + | Alice | /upload/file.txt | + | Brian | /Shares/upload/file.txt | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as sharer copying a file inside a folder changes its etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And using DAV path + And user "Alice" has created folder "/upload" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/file.txt" + And user "Alice" has stored etag of element "/upload/file.txt" on path "/upload/renamed.txt" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + And user "Brian" has stored etag of element "/Shares/upload/file.txt" + And user "Brian" has stored etag of element "/Shares/upload/file.txt" on path "/Shares/upload/renamed.txt" + When user "Alice" copies file "/upload/file.txt" to "/upload/renamed.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/renamed.txt | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + | Brian | /Shares/upload/renamed.txt | + And these etags should not have changed: + | user | path | + | Alice | /upload/file.txt | + | Brian | /Shares/upload/file.txt | + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature new file mode 100644 index 00000000000..c907fb2f991 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature @@ -0,0 +1,131 @@ +@api +Feature: propagation of etags when creating folders + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + + @issue-product-280 + Scenario Outline: creating a folder inside a folder changes its etag + Given using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/folder" + When user "Alice" creates folder "/folder/new" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /folder | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: creating an invalid folder inside a folder should not change any etags + Given using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has created folder "/folder/sub" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/folder" + And user "Alice" has stored etag of element "/folder/sub" + When user "Alice" creates folder "/folder/sub/.." using the WebDAV API + Then the HTTP status code should be "405" + And these etags should not have changed: + | user | path | + | Alice | / | + | Alice | /folder | + | Alice | /folder/sub | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: as share receiver creating a folder inside a folder received as a share changes its etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has shared folder "/folder" with user "Brian" + And user "Brian" has accepted share "/folder" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/folder" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/folder" + When user "Brian" creates folder "/Shares/folder/new" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /folder | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/folder | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as a sharer creating a folder inside a shared folder changes etag for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has shared folder "/folder" with user "Brian" + And user "Brian" has accepted share "/folder" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/folder" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/folder" + When user "Alice" creates folder "/folder/new" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /folder | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/folder | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: creating a folder in a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has created folder "/folder" + And user "Alice" has created a public link share with settings + | path | folder | + | permissions | create | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/folder" + When the public creates folder "created-by-public" using the new public WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /folder | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature new file mode 100644 index 00000000000..3536355ecf1 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreFromTrash.feature @@ -0,0 +1,94 @@ +@api +Feature: propagation of etags when restoring a file or folder from trash + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/upload" + + + Scenario Outline: restoring a file to its original location changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has deleted file "/upload/sub/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" restores the file with original path "/upload/sub/file.txt" using the trashbin API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: restoring a file to an other location changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/restore" + And user "Alice" has created folder "/restore/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has deleted file "/upload/sub/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/restore" + And user "Alice" has stored etag of element "/restore/sub" + When user "Alice" restores the file with original path "/upload/sub/file.txt" to "/restore/sub/file.txt" using the trashbin API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /restore | + | Alice | /restore/sub | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: restoring a folder to its original location changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has deleted folder "/upload/sub/toDelete" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" restores the folder with original path "/upload/sub/toDelete" using the trashbin API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: restoring a folder to an other location changes the etags of all parents + Given using DAV path + And user "Alice" has created folder "/upload/sub" + And user "Alice" has created folder "/upload/sub/toDelete" + And user "Alice" has deleted folder "/upload/sub/toDelete" + And user "Alice" has created folder "/restore" + And user "Alice" has created folder "/restore/sub" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/restore" + And user "Alice" has stored etag of element "/restore/sub" + When user "Alice" restores the folder with original path "/upload/sub/toDelete" to "/restore/sub/toDelete" using the trashbin API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /restore | + | Alice | /restore/sub | + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature new file mode 100644 index 00000000000..c4c64e8f3fb --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/restoreVersion.feature @@ -0,0 +1,24 @@ +@api @issue-product-280 +Feature: propagation of etags when restoring a version of a file + + Background: + Given using OCS API version "2" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + + @skipOnStorage:ceph @skipOnStorage:scality @issue-files_primary_s3-387 + Scenario: Restoring a file changes the etags of all parents + Given user "Alice" has created folder "/upload" + And user "Alice" has created folder "/upload/sub" + And user "Alice" has uploaded file with content "uploaded content" to "/upload/sub/file.txt" + And user "Alice" has uploaded file with content "changed content" to "/upload/sub/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/sub" + When user "Alice" restores version index "1" of file "/upload/sub/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/sub | diff --git a/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature b/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature new file mode 100644 index 00000000000..c9c96a68d10 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature @@ -0,0 +1,179 @@ +@api +Feature: propagation of etags when uploading data + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And the administrator has set the default folder for received shares to "Shares" + And parameter "shareapi_auto_accept_share" of app "core" has been set to "no" + And user "Alice" has created folder "/upload" + + @issue-product-280 + Scenario Outline: uploading a file inside a folder changes its etag + Given using DAV path + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When user "Alice" uploads file with content "uploaded content" to "/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: overwriting a file inside a folder changes its etag + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Alice" has stored etag of element "/upload/file.txt" + When user "Alice" uploads file with content "new content" to "/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Alice | /upload/file.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-product-280 + Scenario Outline: as share receiver uploading a file inside a received shared folder should update etags for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Brian" uploads file with content "uploaded content" to "/Shares/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: as sharer uploading a file inside a shared folder should update etags for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Alice" uploads file with content "uploaded content" to "/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as share receiver overwriting a file inside a received shared folder should update etags for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Brian" uploads file with content "new content" to "/Shares/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: as sharer overwriting a file inside a shared folder should update etags for all collaborators + Given user "Brian" has been created with default attributes and without skeleton files + And using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "/upload/file.txt" + And user "Alice" has shared folder "/upload" with user "Brian" + And user "Brian" has accepted share "/upload" offered by user "Alice" + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + And user "Brian" has stored etag of element "/" + And user "Brian" has stored etag of element "/Shares" + And user "Brian" has stored etag of element "/Shares/upload" + When user "Alice" uploads file with content "new content" to "/upload/file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + | Brian | / | + | Brian | /Shares | + | Brian | /Shares/upload | + Examples: + | dav_version | + | old | + | new | + + @issue-product-280 + Scenario Outline: uploading a file into a publicly shared folder changes its etag for the sharer + Given using DAV path + And user "Alice" has created a public link share with settings + | path | upload | + | permissions | create | + And user "Alice" has stored etag of element "/" + And user "Alice" has stored etag of element "/upload" + When the public uploads file "file.txt" with content "new content" using the new public WebDAV API + Then the HTTP status code should be "201" + And these etags should have changed: + | user | path | + | Alice | / | + | Alice | /upload | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature new file mode 100644 index 00000000000..34850981d3c --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocks.feature @@ -0,0 +1,187 @@ +@api @issue-ocis-reva-172 +Feature: there can be only one exclusive lock on a resource + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: a second lock cannot be set on a folder when its exclusively locked + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | exclusive | + When user "Alice" locks file "textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + @notToImplementOnOCIS + Scenario Outline: if a parent resource is exclusively locked a child resource cannot be locked again + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | exclusive | + When user "Alice" locks folder "PARENT/CHILD" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: if a parent resource is exclusively locked with depth 0 a child resource can be locked again + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | exclusive | + | depth | 0 | + When user "Alice" locks folder "PARENT/CHILD" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 1 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @skipOnOcV10 @issue-34358 @notToImplementOnOCIS + Scenario Outline: if a child resource is exclusively locked a parent resource cannot be locked again + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | exclusive | + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: if a child resource is exclusively locked a parent resource can be locked with depth 0 + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | exclusive | + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + | depth | 0 | + Then the HTTP status code should be "200" + And 1 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @files_sharing-app-required + Scenario Outline: a share receiver cannot lock a resource exclusively locked by itself + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has locked file "textfile0 (2).txt" setting the following properties + | lockscope | exclusive | + When user "Brian" locks file "textfile0 (2).txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile0 (2).txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + @files_sharing-app-required + Scenario Outline: a share receiver cannot lock a resource exclusively locked by the owner + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | exclusive | + When user "Brian" locks file "textfile0 (2).txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile0 (2).txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + @files_sharing-app-required + Scenario Outline: a share owner cannot lock a resource exclusively locked by a share receiver + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has locked file "textfile0 (2).txt" setting the following properties + | lockscope | exclusive | + When user "Alice" locks file "textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "423" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile0 (2).txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocksOc10Issue34358.feature b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocksOc10Issue34358.feature new file mode 100644 index 00000000000..5cd973e747c --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/exclusiveLocksOc10Issue34358.feature @@ -0,0 +1,22 @@ +@api @notToImplementOnOCIS @issue-ocis-reva-172 +Feature: there can be only one exclusive lock on a resource + + @issue-34358 + Scenario Outline: if a child resource is exclusively locked a parent resource cannot be locked again + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And using DAV path + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | exclusive | + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 2 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks/folder.feature b/tests/acceptance/features/coreApiWebdavLocks/folder.feature new file mode 100644 index 00000000000..d87871b046d --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/folder.feature @@ -0,0 +1,128 @@ +@api @issue-ocis-reva-172 +Feature: lock folders + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest @notToImplementOnOCIS + Scenario Outline: upload to a locked folder + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/FOLDER/textfile.txt" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/FOLDER/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: upload to a subfolder of a locked folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/CHILD/textfile.txt" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/CHILD/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @smokeTest @notToImplementOnOCIS + Scenario Outline: create folder in a locked folder + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + When user "Alice" creates folder "/FOLDER/new-folder" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" folder "/FOLDER/new-folder" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: create folder in a subfolder of a locked folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" creates folder "/PARENT/CHILD/new-folder" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" folder "/PARENT/CHILD/new-folder" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: Move file out of a locked folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" moves file "/PARENT/parent.txt" to "/parent.txt" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/parent.txt" should not exist + But as "Alice" file "/PARENT/parent.txt" should exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: Move file out of a locked sub folder one level higher into locked parent folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" moves file "/PARENT/CHILD/child.txt" to "/PARENT/child.txt" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/child.txt" should not exist + But as "Alice" file "/PARENT/CHILD/child.txt" should exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: lockdiscovery of a locked folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" gets the following properties of folder "PARENT" using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response to user "Alice" should match "" + And the value of the item "//d:locktoken/d:href" in the response to user "Alice" should match "/^opaquelocktoken:[a-z0-9-]+$/" + Examples: + | dav-path | lock-scope | lock-root | + | old | shared | /%base_path%\/remote.php\/webdav\/PARENT$/ | + | old | exclusive | /%base_path%\/remote.php\/webdav\/PARENT$/ | + | new | shared | /%base_path%\/remote.php\/dav\/files\/%username%\/PARENT$/ | + | new | exclusive | /%base_path%\/remote.php\/dav\/files\/%username%\/PARENT$/ | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavLocks/publicLink.feature b/tests/acceptance/features/coreApiWebdavLocks/publicLink.feature new file mode 100644 index 00000000000..bf020e32b22 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/publicLink.feature @@ -0,0 +1,100 @@ +@api @smokeTest @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: persistent-locking in case of a public link + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + + @smokeTest + Scenario Outline: Uploading a file into a locked public folder + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created a public link share of folder "FOLDER" with change permission + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + When the public uploads file "/test.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/FOLDER/test.txt" should not exist + Examples: + | dav-path | lock-scope | public-webdav-api-version | + | old | shared | old | + | old | exclusive | old | + | new | shared | old | + | new | exclusive | old | + | old | shared | new | + | old | exclusive | new | + | new | shared | new | + | new | exclusive | new | + + + Scenario Outline: Uploading a file into a locked subfolder of a public folder + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + And the public has uploaded file "test.txt" with content "test" + When the public uploads file "CHILD/test.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/CHILD/test.txt" should not exist + But the content of file "/PARENT/test.txt" for user "Alice" should be "test" + Examples: + | public-webdav-api-version | lock-scope | + | new | shared | + | new | exclusive | + + @smokeTest + Scenario Outline: Overwrite a file inside a locked public folder + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public uploads file "parent.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "423" + And the content of file "/PARENT/parent.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | public-webdav-api-version | lock-scope | + | new | shared | + | new | exclusive | + + + Scenario Outline: Overwrite a file inside a locked subfolder of a public folder + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + And the public has uploaded file "parent.txt" with content "changed text" + When the public uploads file "CHILD/child.txt" with content "test" using the public WebDAV API + Then the HTTP status code should be "423" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "changed text" + But the content of file "/PARENT/CHILD/child.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | public-webdav-api-version | lock-scope | + | new | shared | + | new | exclusive | + + @smokeTest + Scenario Outline: Public locking is not supported + Given user "Alice" has created a public link share of folder "PARENT" with change permission + When the public locks "/CHILD" in the last public link shared folder using the public WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "405" + And the value of the item "//s:message" in the response should be "Locking not allowed from public endpoint" + Examples: + | public-webdav-api-version | lock-scope | + | old | shared | + | old | exclusive | + + Examples: + | public-webdav-api-version | lock-scope | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks/publicLinkLockdiscovery.feature b/tests/acceptance/features/coreApiWebdavLocks/publicLinkLockdiscovery.feature new file mode 100644 index 00000000000..da02828a22a --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/publicLinkLockdiscovery.feature @@ -0,0 +1,127 @@ +@api @smokeTest @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: LOCKDISCOVERY for public links + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + + + Scenario Outline: lockdiscovery root of public link when root is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery subfolder of a locked public link when root is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/CHILD" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery subfolder of a public link when subfolder is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/CHILD" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/CHILD$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery file in a subfolder of a public link when subfolder is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/CHILD/child.txt" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/CHILD$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery file in a subfolder of a public link when root is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/CHILD/child.txt" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery file in a subfolder of a public link when the file is locked + Given user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT/CHILD/child.txt" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/CHILD/child.txt" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/CHILD\/child.txt$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | + + + Scenario Outline: lockdiscovery file in a subfolder of a public link when the folder above the public link is locked + Given user "Alice" has created a public link share of folder "PARENT/CHILD" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public gets the following properties of entry "/child.txt" in the last created public link using the WebDAV API + | propertyName | + | d:lockdiscovery | + Then the HTTP status code should be "200" + And the value of the item "//d:lockroot/d:href" in the response should match "/%base_path%\/public.php\/webdav\/$/" + And the item "//d:locktoken/d:href" in the response should not exist + And the value of the item "//d:timeout" in the response should match "/Second-\d+/" + Examples: + | lock-scope | + | shared | + | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature b/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature new file mode 100644 index 00000000000..40f7cd43663 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/requestsWithToken.feature @@ -0,0 +1,132 @@ +@api @issue-ocis-reva-172 +Feature: actions on a locked item are possible if the token is sent with the request + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest @notToImplementOnOCIS + Scenario Outline: rename a file in a locked folder + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" moves file "/PARENT/parent.txt" to "/PARENT/renamed-file.txt" sending the locktoken of folder "PARENT" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/parent.txt" should not exist + But as "Alice" file "/PARENT/renamed-file.txt" should exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @smokeTest @notToImplementOnOCIS + Scenario Outline: move a file into a locked folder + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + When user "Alice" moves file "/PARENT/parent.txt" to "/FOLDER/moved-file.txt" sending the locktoken of folder "FOLDER" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/parent.txt" should not exist + But as "Alice" file "/FOLDER/moved-file.txt" should exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @smokeTest @notToImplementOnOCIS + Scenario Outline: move a file into a locked folder is impossible when using the wrong token + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + When user "Alice" moves file "/PARENT/parent.txt" to "/FOLDER/moved-file.txt" sending the locktoken of folder "PARENT/CHILD" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/parent.txt" should exist + But as "Alice" file "/FOLDER/moved-file.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @skipOnOcV10 @issue-34338 @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: share receiver cannot rename a file in a folder locked by the owner even when sending the locktoken + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/PARENT/parent.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has shared folder "/PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Brian" moves file "Shares/PARENT/parent.txt" to "Shares/PARENT/renamed-file.txt" sending the locktoken of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/parent.txt" should exist + But as "Alice" file "/PARENT/renamed-file.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @files_sharing-app-required @notToImplementOnOCIS + Scenario Outline: public cannot overwrite a file in a folder locked by the owner even when sending the locktoken + Given user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public uploads file "parent.txt" with content "test" sending the locktoken of file "PARENT" of user "Alice" using the public WebDAV API + Then the HTTP status code should be "" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "ownCloud test text file parent" + Examples: + | lock-scope | webdav_api_version | http_status_code | + | shared | old | 423 | + | exclusive | old | 423 | + | shared | new | 423 | + | exclusive | new | 423 | + + @skipOnOcV10 @issue-34360 @files_sharing-app-required + Scenario Outline: two users having both a shared lock can use the resource + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "textfile0.txt" + And user "Brian" has uploaded file with content "some data" to "textfile0.txt" + And user "Alice" has shared file "/textfile0.txt" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | shared | + And user "Brian" has locked file "Shares/textfile0.txt" setting the following properties + | lockscope | shared | + When user "Alice" uploads file with content "from user 0" to "textfile0.txt" sending the locktoken of file "textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "textfile0.txt" for user "Alice" should be "from user 0" + And the content of file "Shares/textfile0.txt" for user "Brian" should be "from user 0" + When user "Brian" uploads file with content "from user 1" to "Shares/textfile0.txt" sending the locktoken of file "Shares/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "textfile0.txt" for user "Alice" should be "from user 1" + And the content of file "Shares/textfile0.txt" for user "Brian" should be "from user 1" + Examples: + | dav-path | + | old | + | new | + + @personalSpace + Examples: + | dav-path | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34338.feature b/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34338.feature new file mode 100644 index 00000000000..9118ecf63c4 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34338.feature @@ -0,0 +1,25 @@ +@api @notToImplementOnOCIS @issue-ocis-reva-172 +Feature: actions on a locked item are possible if the token is sent with the request + + @issue-34338 @files_sharing-app-required + Scenario Outline: share receiver cannot rename a file in a folder locked by the owner even when sending the locktoken + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Brian" moves file "PARENT/parent.txt" to "PARENT/renamed-file.txt" sending the locktoken of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/PARENT/parent.txt" should not exist + But as "Alice" file "/PARENT/renamed-file.txt" should exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34360.feature b/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34360.feature new file mode 100644 index 00000000000..e9aafe396ff --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks/requestsWithTokenOc10Issue34360.feature @@ -0,0 +1,29 @@ +@api @notToImplementOnOCIS @issue-ocis-reva-172 +Feature: actions on a locked item are possible if the token is sent with the request + + @issue-34360 @files_sharing-app-required + Scenario Outline: two users having both a shared lock can use the resource + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Brian" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | shared | + And user "Brian" has locked file "textfile0 (2).txt" setting the following properties + | lockscope | shared | + When user "Alice" uploads file with content "from user 0" to "textfile0.txt" sending the locktoken of file "textfile0.txt" using the WebDAV API + Then the HTTP status code should be "423" + And the content of file "textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "textfile0 (2).txt" for user "Brian" should be "ownCloud test text file 0" + When user "Brian" uploads file with content "from user 1" to "textfile0 (2).txt" sending the locktoken of file "textfile0 (2).txt" using the WebDAV API + Then the HTTP status code should be "423" + And the content of file "textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "textfile0 (2).txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | dav-path | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToRoot.feature b/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToRoot.feature new file mode 100644 index 00000000000..1303b620ef3 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToRoot.feature @@ -0,0 +1,110 @@ +@api @files_sharing-app-required @issue-ocis-reva-172 @issue-ocis-reva-11 @notToImplementOnOCIS +Feature: lock should propagate correctly if a share is reshared + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And user "Alice" has created folder "PARENT" + And user "Brian" has created folder "PARENT" + And user "Carol" has created folder "PARENT" + + + Scenario Outline: upload to a share that was locked by owner + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has shared folder "PARENT (2)" with user "Carol" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload overwriting to a share that was locked by owner + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Brian" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Carol" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has shared folder "PARENT (2)" with user "Carol" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/parent.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/parent.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/parent.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "ownCloud test text file parent" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: public uploads to a reshared share that was locked by original owner + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has shared folder "PARENT (2)" with user "Carol" + And user "Carol" has created a public link share of folder "PARENT (2)" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public uploads file "test.txt" with content "test" using the new public WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/test.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload to a share that was locked by owner but renamed before + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has shared folder "PARENT (2)" with user "Carol" + And user "Brian" has moved folder "/PARENT (2)" to "/PARENT-renamed" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/PARENT-renamed/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload to a share that was locked by the resharing user + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has shared folder "PARENT (2)" with user "Carol" + And user "Brian" has locked folder "PARENT (2)" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/PARENT (2)/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToShares.feature b/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToShares.feature new file mode 100644 index 00000000000..32ee430b52d --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks2/resharedSharesToShares.feature @@ -0,0 +1,122 @@ +@api @files_sharing-app-required @issue-ocis-reva-172 @issue-ocis-reva-11 @notToImplementOnOCIS +Feature: lock should propagate correctly if a share is reshared + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + | Carol | + And user "Alice" has created folder "PARENT" + And user "Brian" has created folder "PARENT" + And user "Carol" has created folder "PARENT" + + + Scenario Outline: upload to a share that was locked by owner + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has shared folder "Shares/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload overwriting to a share that was locked by owner + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Brian" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Carol" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has shared folder "Shares/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/parent.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/parent.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/parent.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "ownCloud test text file parent" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: public uploads to a reshared share that was locked by original owner + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has shared folder "Shares/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + And user "Carol" has created a public link share of folder "Shares/PARENT" with change permission + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When the public uploads file "test.txt" with content "test" using the new public WebDAV API + Then the HTTP status code should be "423" + And as "Alice" file "/PARENT/test.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload to a share that was locked by owner but renamed before + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has shared folder "Shares/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + And user "Brian" has moved folder "/Shares/PARENT" to "/PARENT-renamed" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/PARENT-renamed/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: upload to a share that was locked by the resharing user + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has shared folder "Shares/PARENT" with user "Carol" + And user "Carol" has accepted share "/PARENT" offered by user "Brian" + And user "Brian" has locked folder "Shares/PARENT" setting the following properties + | lockscope | | + When user "Carol" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/textfile.txt" using the WebDAV API + And user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/PARENT/textfile.txt" using the WebDAV API + And user "Alice" uploads file "filesForUpload/textfile.txt" to "/PARENT/textfile.txt" using the WebDAV API + Then the HTTP status code of responses on all endpoints should be "423" + And as "Alice" file "/PARENT/textfile.txt" should not exist + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks2/setTimeout.feature b/tests/acceptance/features/coreApiWebdavLocks2/setTimeout.feature new file mode 100644 index 00000000000..b0c09f773ad --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks2/setTimeout.feature @@ -0,0 +1,126 @@ +@api @smokeTest @public_link_share-feature-required @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: set timeouts of LOCKS + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: do not set timeout on folder and check the default timeout + Given using DAV path + And parameter "lock_timeout_default" of app "core" has been set to "" + And parameter "lock_timeout_max" of app "core" has been set to "" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | exclusive | + Then the HTTP status code should be "200" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/CHILD" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/parent.txt" should match "" + # consider a drift of up to 9 seconds between setting the lock and retrieving it + Examples: + | dav-path | default-timeout | max-timeout | result | + | old | 120 | 3600 | /Second-(120\|11[1-9])$/ | + | old | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + | new | 120 | 3600 | /Second-(120\|11[1-9])$/ | + | new | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | default-timeout | max-timeout | result | + | spaces | 120 | 3600 | /Second-(120\|11[1-9])$/ | + | spaces | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + + + Scenario Outline: set timeout on folder + Given using DAV path + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/CHILD" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | timeout | result | + | spaces | second-999 | /Second-\d{3}$/ | + | spaces | second-99999999 | /Second-\d{5}$/ | + | spaces | infinite | /Second-\d{5}$/ | + | spaces | second--1 | /Second-\d{5}$/ | + | spaces | second-0 | /Second-\d{4}$/ | + + + Scenario Outline: set timeout over the maximum on folder + Given using DAV path + And parameter "lock_timeout_default" of app "core" has been set to "" + And parameter "lock_timeout_max" of app "core" has been set to "" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/CHILD" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/parent.txt" should match "" + Examples: + | dav-path | timeout | default-timeout | max-timeout | result | + | old | second-600 | 120 | 3600 | /Second-(600\|59[1-9])$/ | + | old | second-600 | 99999 | 3600 | /Second-(600\|59[1-9])$/ | + | old | second-10000 | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | old | second-10000 | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + | old | infinite | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | old | infinite | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + | new | second-600 | 120 | 3600 | /Second-(600\|59[1-9])$/ | + | new | second-600 | 99999 | 3600 | /Second-(600\|59[1-9])$/ | + | new | second-10000 | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | new | second-10000 | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + | new | infinite | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | new | infinite | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | timeout | default-timeout | max-timeout | result | + | spaces | second-600 | 120 | 3600 | /Second-(600\|59[1-9])$/ | + | spaces | second-600 | 99999 | 3600 | /Second-(600\|59[1-9])$/ | + | spaces | second-10000 | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | spaces | second-10000 | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + | spaces | infinite | 120 | 3600 | /Second-(3600\|359[1-9])$/ | + | spaces | infinite | 99999 | 3600 | /Second-(3600\|359[1-9])$/ | + + @files_sharing-app-required + Scenario Outline: as owner set timeout on folder as public check it + Given using DAV path + And user "Alice" has created a public link share of folder "PARENT" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as a public the lock discovery property "//d:timeout" of the folder "/" should match "" + And as a public the lock discovery property "//d:timeout" of the folder "/CHILD" should match "" + And as a public the lock discovery property "//d:timeout" of the folder "/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | diff --git a/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToRoot.feature b/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToRoot.feature new file mode 100644 index 00000000000..81b9602b4a2 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToRoot.feature @@ -0,0 +1,62 @@ +@api @smokeTest @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: set timeouts of LOCKS on shares + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Brian" has created folder "PARENT" + And user "Brian" has created folder "PARENT/CHILD" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: as owner set timeout on folder as receiver check it + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "PARENT (2)" should match "" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "PARENT (2)/CHILD" should match "" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "PARENT (2)/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | + + + Scenario Outline: as share receiver set timeout on folder as owner check it + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" locks folder "PARENT (2)" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/CHILD" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | diff --git a/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToShares.feature b/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToShares.feature new file mode 100644 index 00000000000..34ca92e8ee6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks2/setTimeoutSharesToShares.feature @@ -0,0 +1,66 @@ +@api @smokeTest @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: set timeouts of LOCKS on shares + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Brian" has created folder "PARENT" + And user "Brian" has created folder "PARENT/CHILD" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: as owner set timeout on folder as receiver check it + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "Shares/PARENT" should match "" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "Shares/PARENT/CHILD" should match "" + And as user "Brian" the lock discovery property "//d:timeout" of the folder "Shares/PARENT/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | + + + Scenario Outline: as share receiver set timeout on folder as owner check it + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" locks folder "Shares/PARENT" using the WebDAV API setting the following properties + | lockscope | shared | + | timeout | | + Then the HTTP status code should be "200" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/CHILD" should match "" + And as user "Alice" the lock discovery property "//d:timeout" of the folder "PARENT/parent.txt" should match "" + Examples: + | dav-path | timeout | result | + | old | second-999 | /Second-\d{3}$/ | + | old | second-99999999 | /Second-\d{5}$/ | + | old | infinite | /Second-\d{5}$/ | + | old | second--1 | /Second-\d{5}$/ | + | old | second-0 | /Second-\d{4}$/ | + | new | second-999 | /Second-\d{3}$/ | + | new | second-99999999 | /Second-\d{5}$/ | + | new | infinite | /Second-\d{5}$/ | + | new | second--1 | /Second-\d{5}$/ | + | new | second-0 | /Second-\d{4}$/ | diff --git a/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature b/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature new file mode 100644 index 00000000000..5766a1592c5 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks3/independentLocks.feature @@ -0,0 +1,108 @@ +@api @issue-ocis-reva-172 +Feature: independent locks + Make sure all locks are independent and don't interact with other items that have the same name + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @notToImplementOnOCIS + Scenario Outline: locking a folder does not lock other items with the same name in other parts of the file system + Given using DAV path + And user "Alice" has created folder "locked" + And user "Alice" has created folder "locked/PARENT" + And user "Alice" has created folder "notlocked" + And user "Alice" has created folder "notlocked/PARENT" + And user "Alice" has created folder "alsonotlocked" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/alsonotlocked/PARENT" + When user "Alice" locks folder "locked/PARENT" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/PARENT/file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/alsonotlocked/PARENT" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/locked/PARENT/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: locking a folder on the root level does not lock other folders with the same name in other parts of the file system + Given using DAV path + And user "Alice" has created folder "notlocked" + And user "Alice" has created folder "notlocked/PARENT" + And user "Alice" has created folder "alsonotlocked" + And user "Alice" has created folder "alsonotlocked/PARENT" + When user "Alice" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "201" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/PARENT/file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/alsonotlocked/PARENT/file.txt" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/PARENT/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: locking a file does not lock other items with the same name in other parts of the file system + Given using DAV path + And user "Alice" has created folder "locked" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/locked/textfile0.txt" + And user "Alice" has created folder "notlocked" + And user "Alice" has created folder "notlocked/textfile0.txt" + When user "Alice" locks file "locked/textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/textfile0.txt/real-file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/textfile0.txt" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/locked/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + + Scenario Outline: locking a file/folder with git specific names does not lock other items with the same name in other parts of the file system + Given using DAV path + And user "Alice" has created folder "locked/" + And user "Alice" has created folder "locked/" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/locked//" + And user "Alice" has created folder "notlocked/" + And user "Alice" has created folder "notlocked/" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "notlocked//" + When user "Alice" locks file "locked/" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked//file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked//" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/locked//" + Examples: + | dav-path | lock-scope | foldername | filename | to-lock | + | old | shared | .git | config | .git | + | old | shared | .git | config | .git/config | + | old | exclusive | .git | config | .git | + | old | exclusive | .git | config | .git/config | + | new | shared | .git | config | .git | + | new | shared | .git | config | .git/config | + | new | exclusive | .git | config | .git | + | new | exclusive | .git | config | .git/config | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | foldername | filename | to-lock | + | spaces | shared | .git | config | .git | + | spaces | shared | .git | config | .git/config | + | spaces | exclusive | .git | config | .git | + | spaces | exclusive | .git | config | .git/config | diff --git a/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToRoot.feature b/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToRoot.feature new file mode 100644 index 00000000000..4e492f6a30b --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToRoot.feature @@ -0,0 +1,91 @@ +@api @issue-ocis-reva-172 @notToImplementOnOCIS +Feature: independent locks + Make sure all locks are independent and don't interact with other items that have the same name + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + + + Scenario Outline: locking a received share does not lock other shares that had the same name on the sharer side (shares from different users) + Given using DAV path + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "toShare" + And user "Brian" has created folder "toShare" + And user "Alice" has shared folder "toShare" with user "Carol" + And user "Brian" has shared folder "toShare" with user "Carol" + When user "Carol" locks folder "/toShare" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/lorem.txt" to "/toShare (2)/file.txt" + But user "Carol" should not be able to upload file "filesForUpload/lorem.txt" to "/toShare/file.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: locking a received share does not lock other shares that had the same name on the sharer side (shares from the same user) + Given using DAV path + And user "Alice" has created folder "locked/" + And user "Alice" has created folder "locked/toShare" + And user "Alice" has created folder "notlocked/" + And user "Alice" has created folder "notlocked/toShare" + And user "Alice" has shared folder "locked/toShare" with user "Brian" + And user "Alice" has shared folder "notlocked/toShare" with user "Brian" + When user "Brian" locks folder "/toShare" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Brian" should be able to upload file "filesForUpload/lorem.txt" to "/toShare (2)/file.txt" + But user "Brian" should not be able to upload file "filesForUpload/lorem.txt" to "/toShare/file.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: locking a file in a received share does not lock other items with the same name in other received shares (shares from different users) + Given using DAV path + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FromAlice" + And user "Brian" has created folder "FromBrian" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/FromAlice/textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/FromBrian/textfile0.txt" + And user "Alice" has shared folder "FromAlice" with user "Carol" + And user "Brian" has shared folder "FromBrian" with user "Carol" + When user "Carol" locks file "/FromBrian/textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/lorem.txt" to "/FromAlice/textfile0.txt" + But user "Carol" should not be able to upload file "filesForUpload/lorem.txt" to "/FromBrian/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: locking a file in a received share does not lock other items with the same name in other received shares (shares from same user) + Given using DAV path + And user "Alice" has created folder "locked/" + And user "Alice" has created folder "notlocked/" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "locked/textfile0.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "notlocked/textfile0.txt" + And user "Alice" has shared folder "locked" with user "Brian" + And user "Alice" has shared folder "notlocked" with user "Brian" + When user "Brian" locks file "/locked/textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Brian" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/textfile0.txt" + But user "Brian" should not be able to upload file "filesForUpload/lorem.txt" to "/locked/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature b/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature new file mode 100644 index 00000000000..bff1b5077c1 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocks3/independentLocksShareToShares.feature @@ -0,0 +1,113 @@ +@api @issue-ocis-reva-172 +Feature: independent locks + Make sure all locks are independent and don't interact with other items that have the same name + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + + @notToImplementOnOCIS + Scenario Outline: locking a received share does not lock other shares that had the same name on the sharer side (shares from different users) + Given using DAV path + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "toShare" + And user "Brian" has created folder "toShare" + And user "Alice" has shared folder "toShare" with user "Carol" + And user "Brian" has shared folder "toShare" with user "Carol" + And user "Carol" has accepted share "/toShare" offered by user "Alice" + And user "Carol" has accepted share "/toShare" offered by user "Brian" + When user "Carol" locks folder "/Shares/toShare" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/lorem.txt" to "/Shares/toShare (2)/file.txt" + But user "Carol" should not be able to upload file "filesForUpload/lorem.txt" to "/Shares/toShare/file.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @notToImplementOnOCIS + Scenario Outline: locking a received share does not lock other shares that had the same name on the sharer side (shares from the same user) + Given using DAV path + And user "Alice" has created folder "locked/" + And user "Alice" has created folder "locked/toShare" + And user "Alice" has created folder "notlocked/" + And user "Alice" has created folder "notlocked/toShare" + And user "Alice" has shared folder "locked/toShare" with user "Brian" + And user "Alice" has shared folder "notlocked/toShare" with user "Brian" + And user "Brian" has accepted the first pending share "/toShare" offered by user "Alice" + And user "Brian" has accepted the next pending share "/toShare" offered by user "Alice" + When user "Brian" locks folder "/Shares/toShare" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Brian" should be able to upload file "filesForUpload/lorem.txt" to "/Shares/toShare (2)/file.txt" + But user "Brian" should not be able to upload file "filesForUpload/lorem.txt" to "/Shares/toShare/file.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: locking a file in a received share does not lock other items with the same name in other received shares (shares from different users) + Given using DAV path + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FromAlice" + And user "Brian" has created folder "FromBrian" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/FromAlice/textfile0.txt" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "/FromBrian/textfile0.txt" + And user "Alice" has shared folder "FromAlice" with user "Carol" + And user "Brian" has shared folder "FromBrian" with user "Carol" + And user "Carol" has accepted share "/FromAlice" offered by user "Alice" + And user "Carol" has accepted share "/FromBrian" offered by user "Brian" + When user "Carol" locks file "/Shares/FromBrian/textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Carol" should be able to upload file "filesForUpload/lorem.txt" to "/Shares/FromAlice/textfile0.txt" + But user "Carol" should not be able to upload file "filesForUpload/lorem.txt" to "/Shares/FromBrian/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + + Scenario Outline: locking a file in a received share does not lock other items with the same name in other received shares (shares from same user) + Given using DAV path + And user "Alice" has created folder "locked/" + And user "Alice" has created folder "notlocked/" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/locked/textfile0.txt" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/notlocked/textfile0.txt" + And user "Alice" has shared folder "locked" with user "Brian" + And user "Alice" has shared folder "notlocked" with user "Brian" + And user "Brian" has accepted share "/locked" offered by user "Alice" + And user "Brian" has accepted share "/notlocked" offered by user "Alice" + When user "Brian" locks file "/Shares/locked/textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And user "Brian" should be able to upload file "filesForUpload/lorem.txt" to "/Shares/notlocked/textfile0.txt" + But user "Brian" should not be able to upload file "filesForUpload/lorem.txt" to "/Shares/locked/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/lockBreakersGroups.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/lockBreakersGroups.feature new file mode 100644 index 00000000000..19d23ab32a6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/lockBreakersGroups.feature @@ -0,0 +1,310 @@ +@api @issue-ocis-2413 @notToImplementOnOCIS +Feature: UNLOCK locked items + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: a group can be added as a lock breaker group + Given using DAV path + And group "grp1" has been created + When the administrator sets parameter "lock-breaker-groups" of app "core" to '["grp1"]' + Then the HTTP status code should be "200" + And group "grp1" should exist as a lock breaker group + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: more than one group can be added as a lock breaker group + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + When the administrator sets parameter "lock-breaker-groups" of app "core" to '["grp1","grp2"]' + Then the HTTP status code should be "200" + And following groups should exist as lock breaker groups + | groups | + | grp1 | + | grp2 | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: member of the lock breakers group can unlock a locked folder shared with them + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has shared folder "FOLDER" with user "Brian" + When user "Brian" unlocks folder "FOLDER" with the last created lock of folder "FOLDER" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "FOLDER" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "FOLDER" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can lock shared folder that was unlocked by the lock breaker group before + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has shared folder "FOLDER" with user "Brian" + And user "Brian" has unlocked folder "FOLDER" with the last created lock of folder "FOLDER" of user "Alice" using the WebDAV API + When user "Brian" locks folder "FOLDER" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 1 locks should be reported for folder "FOLDER" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can unlock a locked file shared with them + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" unlocks file "textfile0.txt" with the last created lock of file "textfile0.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can lock shared file that was unlocked by the lock breaker group before + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Brian" has unlocked file "textfile0.txt" with the last created lock of folder "textfile0.txt" of user "Alice" using the WebDAV API + When user "Brian" locks file "textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 1 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as a member of lock breaker group unlocking a file in a share locked by the file owner is possible + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks file "PARENT/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as a member of lock breaker group unlocking a folder in a share locked by the folder owner is possible + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "CHILD" + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks folder "PARENT/CHILD" with the last created lock of folder "PARENT/CHILD" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT/CHILD" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of different lock breaker groups can lock and unlock same folder shared to them + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1","grp2"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Alice" has shared folder "PARENT" with user "Carol" + When user "Brian" unlocks folder "PARENT" with the last created lock of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + When user "Brian" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + And 1 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Then the HTTP status code should be "200" + When user "Carol" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Brian" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of different lock breaker groups can lock and unlock same file shared to them + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1","grp2"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile.txt" with user "Brian" + And user "Alice" has shared file "textfile.txt" with user "Carol" + When user "Brian" unlocks file "textfile.txt" with the last created lock of file "textfile.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + When user "Brian" locks file "textfile.txt" using the WebDAV API setting the following properties + | lockscope | | + And 1 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + Then the HTTP status code should be "200" + When user "Carol" unlocks file "textfile.txt" with the last created lock of file "textfile.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of lock breaker group can unlock a folder in group sharing + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "PARENT" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with group "grp2" + When user "Carol" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + When user "Brian" unlocks folder "PARENT" with the last created lock of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of lock breaker group can unlock a file in group sharing + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile0.txt" with group "grp2" + When user "Carol" unlocks file "textfile0.txt" with the last created lock of file "textfile0.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile0.txt" of user "Carol" by the WebDAV API + When user "Brian" unlocks file "textfile0.txt" with the last created lock of file "textfile0.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile0.txt" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature new file mode 100644 index 00000000000..61a65a40f62 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlock.feature @@ -0,0 +1,138 @@ +@api @issue-ocis-reva-172 +Feature: UNLOCK locked items + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @smokeTest @notToImplementOnOCIS + Scenario Outline: unlock a single lock set by the user itself + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" unlocks the last created lock of folder "PARENT" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: unlock one of multiple locks set by the user itself + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | shared | + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | shared | + When user "Alice" unlocks the last created lock of file "textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | + | old | + | new | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | + | spaces | + + @notToImplementOnOCIS + Scenario Outline: unlocking a file that was locked by the user locking the folder above is not possible + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + When user "Alice" unlocks file "PARENT/CHILD/child.txt" with the last created lock of folder "PARENT/CHILD" using the WebDAV API + Then the HTTP status code should be "204" + And 1 locks should be reported for file "PARENT/CHILD/child.txt" of user "Alice" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @skipOnOcV10 @issue-34302 @files_sharing-app-required + Scenario Outline: as public unlocking a file in a share that was locked by the file owner is not possible. To unlock use the owners locktoken + Given user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + When the public unlocks file "/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + Examples: + | lock-scope | + | shared | + | exclusive | + + @notToImplementOnOCIS + Scenario Outline: unlocking a file or folder does not unlock another folder with the same name in another part of the file system + Given using DAV path + And user "Alice" has created folder "locked" + And user "Alice" has created folder "locked/PARENT" + And user "Alice" has created folder "notlocked" + And user "Alice" has created folder "notlocked/PARENT" + And user "Alice" has created folder "alsonotlocked" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/alsonotlocked/PARENT" + And user "Alice" has locked folder "locked/PARENT" setting the following properties + | lockscope | | + And user "Alice" has locked folder "notlocked/PARENT" setting the following properties + | lockscope | | + And user "Alice" has locked file "alsonotlocked/PARENT" setting the following properties + | lockscope | | + When user "Alice" unlocks the last created lock of folder "notlocked/PARENT" using the WebDAV API + And user "Alice" unlocks the last created lock of file "alsonotlocked/PARENT" using the WebDAV API + Then user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/PARENT/file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/alsonotlocked/PARENT" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/locked/PARENT/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: unlocking a file or folder does not unlock another file with the same name in another part of the file system + Given using DAV path + And user "Alice" has created folder "locked" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/locked/textfile0.txt" + And user "Alice" has created folder "notlocked" + And user "Alice" has created folder "notlocked/textfile0.txt" + And user "Alice" has locked file "locked/textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has locked file "notlocked/textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + When user "Alice" unlocks the last created lock of file "notlocked/textfile0.txt" using the WebDAV API + And user "Alice" unlocks the last created lock of file "textfile0.txt" using the WebDAV API + Then user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/notlocked/textfile0.txt/real-file.txt" + And user "Alice" should be able to upload file "filesForUpload/lorem.txt" to "/textfile0.txt" + But user "Alice" should not be able to upload file "filesForUpload/lorem.txt" to "/locked/textfile0.txt" + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockOc10Issue34302.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockOc10Issue34302.feature new file mode 100644 index 00000000000..957aa584772 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockOc10Issue34302.feature @@ -0,0 +1,19 @@ +@api @notToImplementOnOCIS @issue-ocis-reva-172 +Feature: UNLOCK locked items + + @issue-34302 @files_sharing-app-required + Scenario Outline: as public unlocking a file in a share that was locked by the file owner is not possible. To unlock use the owners locktoken + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has created a public link share of folder "PARENT" with change permission + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + When the public unlocks file "/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "405" + #Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + Examples: + | lock-scope | + | shared | + | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToRoot.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToRoot.feature new file mode 100644 index 00000000000..f12440d25fc --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToRoot.feature @@ -0,0 +1,143 @@ +@api @issue-ocis-reva-172 @files_sharing-app-required @notToImplementOnOCIS +Feature: UNLOCK locked items (sharing) + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: as share receiver unlocking a shared file locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + When user "Brian" unlocks file "parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as share receiver unlocking a file in a share locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Brian" has created folder "PARENT" + And user "Brian" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks file "PARENT (2)/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT (2)/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as share receiver unlocking a shared folder locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 3 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 3 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as share receiver unlock a shared file + Given using DAV path + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has locked file "parent.txt" setting the following properties + | lockscope | | + When user "Brian" unlocks the last created lock of file "parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as owner unlocking a shared file locked by the receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has locked file "parent.txt" setting the following properties + | lockscope | | + When user "Alice" unlocks file "PARENT/parent.txt" with the last created lock of file "parent.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as owner unlocking a file in a share that was locked by the share receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + When user "Alice" unlocks file "PARENT/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as owner unlocking a shared folder locked by the share receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has locked folder "PARENT" setting the following properties + | lockscope | | + When user "Alice" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 3 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 3 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature new file mode 100644 index 00000000000..97d4507278d --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavLocksUnlock/unlockSharingToShares.feature @@ -0,0 +1,180 @@ +@api @issue-ocis-reva-172 @files_sharing-app-required +Feature: UNLOCK locked items (sharing) + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + + + Scenario Outline: as share receiver unlocking a shared file locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + When user "Brian" unlocks file "Shares/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "Shares/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | pending_share_path | + | old | shared | /parent.txt | + | old | exclusive | /parent.txt | + | new | shared | /parent.txt | + | new | exclusive | /parent.txt | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | pending_share_path | + | spaces | shared | /parent.txt | + | spaces | exclusive | /parent.txt | + + + Scenario Outline: as share receiver unlocking a file in a share locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" unlocks file "Shares/PARENT/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "Shares/PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + @notToImplementOnOCIS + Scenario Outline: as share receiver unlocking a shared folder locked by the file owner is not possible. To unlock use the owners locktoken + Given using DAV path + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + When user "Brian" unlocks folder "Shares/PARENT" with the last created lock of folder "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 3 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 3 locks should be reported for folder "Shares/PARENT" of user "Brian" by the WebDAV API + And 2 locks should be reported for folder "Shares/PARENT/CHILD" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "Shares/PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as share receiver unlock a shared file + Given using DAV path + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + And user "Brian" has locked file "Shares/parent.txt" setting the following properties + | lockscope | | + When user "Brian" unlocks the last created lock of file "Shares/parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "Shares/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | pending_share_path | + | old | shared | /parent.txt | + | old | exclusive | /parent.txt | + | new | shared | /parent.txt | + | new | exclusive | /parent.txt | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | pending_share_path | + | spaces | shared | /parent.txt | + | spaces | exclusive | /parent.txt | + + + Scenario Outline: as owner unlocking a shared file locked by the receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has shared file "PARENT/parent.txt" with user "Brian" + And user "Brian" has accepted share "" offered by user "Alice" + And user "Brian" has locked file "Shares/parent.txt" setting the following properties + | lockscope | | + When user "Alice" unlocks file "PARENT/parent.txt" with the last created lock of file "Shares/parent.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "Shares/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | pending_share_path | + | old | shared | /parent.txt | + | old | exclusive | /parent.txt | + | new | shared | /parent.txt | + | new | exclusive | /parent.txt | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | pending_share_path | + | spaces | shared | /parent.txt | + | spaces | exclusive | /parent.txt | + + + Scenario Outline: as owner unlocking a file in a share that was locked by the share receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has locked file "Shares/PARENT/parent.txt" setting the following properties + | lockscope | | + When user "Alice" unlocks file "PARENT/parent.txt" with the last created lock of file "Shares/PARENT/parent.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "Shares/PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + @personalSpace @skipOnOcV10 + Examples: + | dav-path | lock-scope | + | spaces | shared | + | spaces | exclusive | + + @notToImplementOnOCIS + Scenario Outline: as owner unlocking a shared folder locked by the share receiver is not possible. To unlock use the receivers locktoken + Given using DAV path + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/CHILD/child.txt" + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Brian" has accepted share "/PARENT" offered by user "Alice" + And user "Brian" has locked folder "Shares/PARENT" setting the following properties + | lockscope | | + When user "Alice" unlocks folder "PARENT" with the last created lock of folder "Shares/PARENT" of user "Brian" using the WebDAV API + Then the HTTP status code should be "403" + And 3 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 2 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 3 locks should be reported for folder "Shares/PARENT" of user "Brian" by the WebDAV API + And 2 locks should be reported for folder "Shares/PARENT/CHILD" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "Shares/PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFileAsync.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFileAsync.feature new file mode 100644 index 00000000000..fa35764eafb --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFileAsync.feature @@ -0,0 +1,278 @@ +@api @issue-ocis-reva-14 @notToImplementOnOCIS +Feature: move (rename) file + As a user + I want to be able to move and rename files asynchronously + So that I can manage my file system + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And the administrator has enabled async operations + + + Scenario Outline: Moving a file + Given user "Alice" has created folder "FOLDER" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/FOLDER/" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + | ETag | /^"[0-9a-f]{1,32}"$/ | + And the content of file "/FOLDER/" for user "Alice" should be "ownCloud test text file 0" + And user "Alice" should not see the following elements + | /textfile0.txt | + Examples: + | destination-file-name | + | नेपाली.txt | + | strängé file.txt | + | C++ file.cpp | + | file #2.txt | + | file ?2.txt | + | sample,1.txt | + + + Scenario: Moving and overwriting a file + Given user "Alice" has uploaded file with content "Welcome to move" to "/fileToMove.txt" + When user "Alice" moves file "/fileToMove.txt" asynchronously to "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + | ETag | /^"[0-9a-f]{1,32}"$/ | + And the content of file "/textfile0.txt" for user "Alice" should be "Welcome to move" + And user "Alice" should not see the following elements + | /fileToMove.txt | + + + Scenario: Moving (renaming) a file to be only different case + When user "Alice" moves file "/textfile0.txt" asynchronously to "/TextFile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + | ETag | /^"[0-9a-f]{1,32}"$/ | + And the content of file "/TextFile0.txt" for user "Alice" should be "ownCloud test text file 0" + And user "Alice" should not see the following elements + | /textfile0.txt | + + + Scenario: Moving (renaming) a file to a file with only different case to an existing file + Given user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + When user "Alice" moves file "/textfile1.txt" asynchronously to "/TextFile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + | ETag | /^"[0-9a-f]{1,32}"$/ | + And the content of file "/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "/TextFile0.txt" for user "Alice" should be "ownCloud test text file 1" + And user "Alice" should not see the following elements + | /textfile1.txt | + + + Scenario: Moving (renaming) a file to a file in a folder with only different case to an existing file + Given user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/PARENT/Parent.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + | ETag | /^"[0-9a-f]{1,32}"$/ | + And the content of file "/PARENT/parent.txt" for user "Alice" should be "ownCloud test text file parent" + And the content of file "/PARENT/Parent.txt" for user "Alice" should be "ownCloud test text file 0" + And user "Alice" should not see the following elements + | /textfile0.txt | + + @files_sharing-app-required + Scenario: Moving a file to a folder with no permissions + Given user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + When user "Alice" moves file "/textfile0.txt" asynchronously to "/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^error$/ | + | errorCode | /^403$/ | + And user "Alice" downloads file "/testshare/textfile0.txt" using the WebDAV API + And the HTTP status code should be "404" + And user "Alice" should see the following elements + | /textfile0.txt | + + @files_sharing-app-required + Scenario: Moving a file to overwrite a file in a folder with no permissions + Given user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "Welcome to overwrite" to "/fileToCopy.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Brian" has copied file "/fileToCopy.txt" to "/testshare/overwritethis.txt" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/testshare/overwritethis.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^error$/ | + | errorCode | /^403$/ | + And the content of file "/testshare/overwritethis.txt" for user "Alice" should be "Welcome to overwrite" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: move file into a not-existing folder + When user "Alice" moves file "/textfile0.txt" asynchronously to "/not-existing/not-existing-file.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^error$/ | + | errorCode | /^409$/ | + | errorMessage | /^The destination node is not found$/ | + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: rename a file into an invalid filename + When user "Alice" moves file "/textfile0.txt" asynchronously to "/a\\a" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: Renaming a file to a path with extension .part should not be possible + When user "Alice" moves file "/textfile0.txt" asynchronously to "/textfile0.part" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should see the following elements + | /textfile0.txt | + But user "Alice" should not see the following elements + | /textfile0.part | + + + Scenario: Checking file id after a move + Given user "Alice" has stored id of file "/textfile0.txt" + And user "Alice" has created folder "FOLDER" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/FOLDER/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And user "Alice" file "/FOLDER/textfile0.txt" should have the previously stored id + And user "Alice" should not see the following elements + | /textfile0.txt | + + + Scenario: disabled async operations leads to original behavior + Given the administrator has disabled async operations + And user "Alice" has created folder "FOLDER" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/FOLDER/fileToMove.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should not be set + | header | + | OC-JobStatus-Location | + And the content of file "/FOLDER/fileToMove.txt" for user "Alice" should be "ownCloud test text file 0" + + + Scenario Outline: enabling async operations does no difference to normal MOVE - Moving a file + Given the administrator has enabled async operations + And user "Alice" has created folder "FOLDER" + And using DAV path + When user "Alice" moves file "/textfile0.txt" to "/FOLDER/fileToMove.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/fileToMove.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: enabling async operations does no difference to normal MOVE - Moving and overwriting a file + Given the administrator has enabled async operations + And user "Alice" has uploaded file with content "Welcome to ownCloud" to "/fileToMove.txt" + And using DAV path + When user "Alice" moves file "/fileToMove.txt" to "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/textfile0.txt" for user "Alice" should be "Welcome to ownCloud" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: enabling async operations does no difference to normal MOVE - Moving a file to a folder with no permissions + Given the administrator has enabled async operations + And using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + When user "Alice" moves file "/textfile0.txt" to "/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should not be able to download file "/testshare/textfile0.txt" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: enabling async operations does no difference to normal MOVE - move file into a not-existing folder + Given the administrator has enabled async operations + And using DAV path + When user "Alice" moves file "/textfile0.txt" to "/not-existing/fileToMove.txt" using the WebDAV API + Then the HTTP status code should be "409" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: enabling async operations does no difference to normal MOVE - rename a file into an invalid filename + Given the administrator has enabled async operations + And using DAV path + When user "Alice" moves file "/textfile0.txt" to "/a\\a" using the WebDAV API + Then the HTTP status code should be "400" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: enabling async operations does no difference to normal MOVE - rename a file into a banned filename + Given the administrator has enabled async operations + And using DAV path + When user "Alice" moves file "/textfile0.txt" to "/.htaccess" using the WebDAV API + Then the HTTP status code should be "403" + Examples: + | dav_version | + | old | + | new | + + #this does not work if firewall app is enabled + #and it also does not work with the php-dev server + @skipOnFirewall + Scenario: Moving and overwriting a file with lazyops + #need to slowdown the request for longer than the timeout + #when doing LazyOps the server does not close the connection + #so we timeout the request and check the job-status + Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToMove.txt" + And the HTTP-Request-timeout is set to 5 seconds + And the MOVE DAV requests are slowed down by 10 seconds + When user "Alice" moves file "/fileToMove.txt" asynchronously to "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^started$/ | diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFileToBlacklistedNameAsync.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFileToBlacklistedNameAsync.feature new file mode 100644 index 00000000000..9f31a815df8 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFileToBlacklistedNameAsync.feature @@ -0,0 +1,55 @@ +@api @issue-ocis-reva-14 @notToImplementOnOCIS +Feature: users cannot move (rename) a file to a blacklisted name + As an administrator + I want to be able to prevent users from moving (renaming) files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And the administrator has enabled async operations + + + Scenario: rename a file to a filename that is banned by default + When user "Alice" moves file "/textfile0.txt" asynchronously to "/.htaccess" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: rename a file to a banned filename + Given the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: rename a file to a filename that matches (or not) blacklisted_files_regex + Given user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + And the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" moves file "/textfile0.txt" asynchronously to these filenames using the webDAV API then the results should be as listed + | filename | http-code | exists | + | .ext | 403 | no | + | filename.ext | 403 | no | + | bannedfilename.txt | 403 | no | + | containsbannedstring | 403 | no | + | this-ContainsBannedString.txt | 403 | no | + | /FOLDER/.ext | 403 | no | + | /FOLDER/filename.ext | 403 | no | + | /FOLDER/bannedfilename.txt | 403 | no | + | /FOLDER/containsbannedstring | 403 | no | + | /FOLDER/this-ContainsBannedString.txt | 403 | no | + | .extension | 202 | yes | + | filename.txt | 202 | yes | + | bannedfilename | 202 | yes | + | bannedfilenamewithoutdot | 202 | yes | + | not-contains-banned-string.txt | 202 | yes | + | /FOLDER/.extension | 202 | yes | + | /FOLDER/filename.txt | 202 | yes | + | /FOLDER/bannedfilename | 202 | yes | + | /FOLDER/bannedfilenamewithoutdot | 202 | yes | + | /FOLDER/not-contains-banned-string.txt | 202 | yes | diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFileToExcludedDirectoryAsync.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFileToExcludedDirectoryAsync.feature new file mode 100644 index 00000000000..40942abb222 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFileToExcludedDirectoryAsync.feature @@ -0,0 +1,59 @@ +@api @issue-ocis-reva-14 @notToImplementOnOCIS +Feature: users cannot move (rename) a file to or into an excluded directory + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to rename an existing file or folder to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And the administrator has enabled async operations + + + Scenario: rename a file to an excluded directory name + Given the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/.github" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: rename a file to an excluded directory name inside a parent directory + Given user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves file "/textfile0.txt" asynchronously to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /textfile0.txt | + + + Scenario: rename a file to a filename that matches (or not) excluded_directories_regex + Given user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + And the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" moves file "/textfile0.txt" asynchronously to these filenames using the webDAV API then the results should be as listed + | filename | http-code | exists | + | endswith.bad | 403 | no | + | thisendswith.bad | 403 | no | + | .git | 403 | no | + | .github | 403 | no | + | containsvirusinthename | 403 | no | + | this-containsvirusinthename.txt | 403 | no | + | /FOLDER/endswith.bad | 403 | no | + | /FOLDER/thisendswith.bad | 403 | no | + | /FOLDER/.git | 403 | no | + | /FOLDER/.github | 403 | no | + | /FOLDER/containsvirusinthename | 403 | no | + | /FOLDER/this-containsvirusinthename.txt | 403 | no | + | endswith.badandotherstuff | 202 | yes | + | thisendswith.badandotherstuff | 202 | yes | + | name.git | 202 | yes | + | name.github | 202 | yes | + | not-contains-virus-in-the-name.txt | 202 | yes | + | /FOLDER/endswith.badandotherstuff | 202 | yes | + | /FOLDER/thisendswith.badandotherstuff | 202 | yes | + | /FOLDER/name.git | 202 | yes | + | /FOLDER/name.github | 202 | yes | + | /FOLDER/not-contains-virus-in-the-name.txt | 202 | yes | diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature new file mode 100644 index 00000000000..bd8cd64dacd --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFolder.feature @@ -0,0 +1,201 @@ +@api @issue-ocis-reva-14 +Feature: move (rename) folder + As a user + I want to be able to move and rename folders + So that I can quickly manage my file system + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Renaming a folder to a backslash should return an error + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" moves folder "/testshare" to "\" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Renaming a folder beginning with a backslash should return an error + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" moves folder "/testshare" to "\testshare" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Renaming a folder including a backslash encoded should return an error + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" moves folder "/testshare" to "/hola\hola" using the WebDAV API + Then the HTTP status code should be "400" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Move a folder into an other one + Given using DAV path + And user "Alice" has created folder "/testshare" + And user "Alice" has created folder "/an-other-folder" + When user "Alice" moves folder "/testshare" to "/an-other-folder/testshare" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should not see the following elements + | /testshare/ | + And user "Alice" should see the following elements + | /an-other-folder/testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Move a folder into a nonexistent one + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" moves folder "/testshare" to "/not-existing/testshare" using the WebDAV API + Then the HTTP status code should be "409" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: renaming folder with dots in the path + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content for file name ending with a dot" to "/abc.txt" + When user "Alice" moves folder "" to "/uploadFolder" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/uploadFolder/abc.txt" for user "Alice" should be "uploaded content for file name ending with a dot" + Examples: + | dav_version | folder_name | + | old | /upload. | + | old | /upload.1 | + | old | /upload...1.. | + | old | /... | + | old | /..upload | + | new | /upload. | + | new | /upload.1 | + | new | /upload...1.. | + | new | /... | + | new | /..upload | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | + | spaces | /upload. | + | spaces | /upload.1 | + | spaces | /upload...1.. | + | spaces | /... | + | spaces | /..upload | + + @issue-ocis-3023 + Scenario Outline: Moving a folder into a sub-folder of itself + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "PARENT/CHILD" + And user "Alice" has uploaded file with content "parent text" to "/PARENT/parent.txt" + And user "Alice" has uploaded file with content "child text" to "/PARENT/CHILD/child.txt" + When user "Alice" moves folder "/PARENT" to "/PARENT/CHILD/PARENT" using the WebDAV API + Then the HTTP status code should be "409" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "parent text" + And the content of file "/PARENT/CHILD/child.txt" for user "Alice" should be "child text" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a folder out of a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created folder "/testshare/testsubfolder" + And user "Brian" has uploaded file with content "test data" to "/testshare/testsubfolder/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + When user "" moves folder "/testshare/testsubfolder" to "/testsubfolder" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testsubfolder/testfile.txt" for user "" should be "test data" + And as "Alice" folder "/testshare/testsubfolder" should not exist + And as "Brian" folder "/testshare/testsubfolder" should not exist + Examples: + | dav_version | mover | + | old | Alice | + | old | Brian | + | new | Alice | + | new | Brian | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a folder into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "" has created folder "/testsubfolder" + And user "" has uploaded file with content "test data" to "/testsubfolder/testfile.txt" + When user "" moves folder "/testsubfolder" to "/testshare/testsubfolder" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testshare/testsubfolder/testfile.txt" for user "Alice" should be "test data" + And the content of file "/testshare/testsubfolder/testfile.txt" for user "Brian" should be "test data" + And as "" file "/testsubfolder" should not exist + Examples: + | dav_version | mover | + | old | Alice | + | old | Brian | + | new | Alice | + | new | Brian | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature new file mode 100644 index 00000000000..e412b6444d1 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToBlacklistedName.feature @@ -0,0 +1,87 @@ +@api @issue-ocis-reva-14 +Feature: users cannot move (rename) a folder to a blacklisted name + As an administrator + I want to be able to prevent users from moving (renaming) folders to specified names + So that I can prevent unwanted folder names existing in the cloud storage + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Rename a folder to a name that is banned by default + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" moves folder "/testshare" to "/.htaccess" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Rename a folder to a banned name + Given using DAV path + And user "Alice" has created folder "/testshare" + And the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" moves folder "/testshare" to "/blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a folder to a folder name that matches (or not) blacklisted_files_regex + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created folder "/FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + And the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Brian" moves folder "/testshare" to these foldernames using the webDAV API then the results should be as listed + | foldername | http-code | exists | + | .ext | 403 | no | + | filename.ext | 403 | no | + | bannedfilename.txt | 403 | no | + | containsbannedstring | 403 | no | + | this-ContainsBannedString.txt | 403 | no | + | /FOLDER/.ext | 403 | no | + | /FOLDER/filename.ext | 403 | no | + | /FOLDER/bannedfilename.txt | 403 | no | + | /FOLDER/containsbannedstring | 403 | no | + | /FOLDER/this-ContainsBannedString.txt | 403 | no | + | .extension | 201 | yes | + | filename.txt | 201 | yes | + | bannedfilename | 201 | yes | + | bannedfilenamewithoutdot | 201 | yes | + | not-contains-banned-string.txt | 201 | yes | + | /FOLDER/.extension | 201 | yes | + | /FOLDER/filename.txt | 201 | yes | + | /FOLDER/bannedfilename | 201 | yes | + | /FOLDER/bannedfilenamewithoutdot | 201 | yes | + | /FOLDER/not-contains-banned-string.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature new file mode 100644 index 00000000000..84c846a16ae --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove1/moveFolderToExcludedDirectory.feature @@ -0,0 +1,90 @@ +@api @issue-ocis-reva-14 +Feature: users cannot move (rename) a folder to or into an excluded directory + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to rename an existing folder to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Rename a folder to an excluded directory name + Given using DAV path + And user "Alice" has created folder "/testshare" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves folder "/testshare" to "/.github" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Rename a folder to an excluded directory name inside a parent directory + Given using DAV path + And user "Alice" has created folder "/testshare" + And user "Alice" has created folder "/FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves folder "/testshare" to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a folder to a folder name that matches (or not) excluded_directories_regex + Given using DAV path + And user "Alice" has created folder "/testshare" + And user "Alice" has created folder "/FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + And the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" moves folder "/testshare" to these foldernames using the webDAV API then the results should be as listed + | foldername | http-code | exists | + | endswith.bad | 403 | no | + | thisendswith.bad | 403 | no | + | .git | 403 | no | + | .github | 403 | no | + | containsvirusinthename | 403 | no | + | this-containsvirusinthename.txt | 403 | no | + | /FOLDER/endswith.bad | 403 | no | + | /FOLDER/thisendswith.bad | 403 | no | + | /FOLDER/.git | 403 | no | + | /FOLDER/.github | 403 | no | + | /FOLDER/containsvirusinthename | 403 | no | + | /FOLDER/this-containsvirusinthename.txt | 403 | no | + | endswith.badandotherstuff | 201 | yes | + | thisendswith.badandotherstuff | 201 | yes | + | name.git | 201 | yes | + | name.github | 201 | yes | + | not-contains-virus-in-the-name.txt | 201 | yes | + | /FOLDER/endswith.badandotherstuff | 201 | yes | + | /FOLDER/thisendswith.badandotherstuff | 201 | yes | + | /FOLDER/name.git | 201 | yes | + | /FOLDER/name.github | 201 | yes | + | /FOLDER/not-contains-virus-in-the-name.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature b/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature new file mode 100644 index 00000000000..9255feef2fd --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove2/moveFile.feature @@ -0,0 +1,580 @@ +@api +Feature: move (rename) file + As a user + I want to be able to move and rename files + So that I can manage my file system + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: Moving a file + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + When user "Alice" moves file "/textfile0.txt" to "/FOLDER/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/FOLDER/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: Moving and overwriting a file + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + When user "Alice" moves file "/textfile0.txt" to "/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/textfile1.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Moving (renaming) a file to be only different case + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + When user "Alice" moves file "/textfile0.txt" to "/TextFile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/textfile0.txt" should not exist + And the content of file "/TextFile0.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: Moving (renaming) a file to a file with only different case to an existing file + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + When user "Alice" moves file "/textfile1.txt" to "/TextFile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "/TextFile0.txt" for user "Alice" should be "ownCloud test text file 1" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Moving (renaming) a file to a file in a folder with only different case to an existing file + Given using DAV path + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file with content "ownCloud test text file parent" to "PARENT/parent.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + When user "Alice" moves file "/textfile1.txt" to "/PARENT/Parent.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "ownCloud test text file parent" + And the content of file "/PARENT/Parent.txt" for user "Alice" should be "ownCloud test text file 1" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "" has uploaded file with content "test data" to "/testfile.txt" + When user "" moves file "/testfile.txt" to "/testshare/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testshare/testfile.txt" for user "Alice" should be "test data" + And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data" + And as "" file "/testfile.txt" should not exist + Examples: + | dav_version | mover | + | old | Alice | + | new | Alice | + | old | Brian | + | new | Brian | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a file out of a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "test data" to "/testshare/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + When user "" moves file "/testshare/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testfile.txt" for user "" should be "test data" + And as "Alice" file "/testshare/testfile.txt" should not exist + And as "Brian" file "/testshare/testfile.txt" should not exist + Examples: + | dav_version | mover | + | old | Alice | + | new | Alice | + | old | Brian | + | new | Brian | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a file to a shared folder with no permissions + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + When user "Alice" moves file "/textfile0.txt" to "/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should not be able to download file "/testshare/textfile0.txt" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Moving a file to overwrite a file in a shared folder with no permissions + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "Welcome to ownCloud" to "fileToCopy.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Brian" has copied file "/fileToCopy.txt" to "/testshare/overwritethis.txt" + When user "Alice" moves file "/textfile0.txt" to "/testshare/overwritethis.txt" using the WebDAV API + Then the HTTP status code should be "403" + And the content of file "/testshare/overwritethis.txt" for user "Alice" should be "Welcome to ownCloud" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: move file into a not-existing folder + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToMove.txt" + When user "Alice" moves file "/fileToMove.txt" to "/not-existing/fileToMove.txt" using the WebDAV API + Then the HTTP status code should be "409" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-211 + Scenario Outline: rename a file into an invalid filename + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToRename.txt" + When user "Alice" moves file "/fileToRename.txt" to "/a\\a" using the WebDAV API + Then the HTTP status code should be "400" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Checking file id after a move + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has stored id of file "/textfile0.txt" + When user "Alice" moves file "/textfile0.txt" to "/FOLDER/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" file "/FOLDER/textfile0.txt" should have the previously stored id + And user "Alice" should not see the following elements + | /textfile0.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @skipOnOcis + Scenario Outline: Checking file id after a move between received shares + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/folderA" + And user "Alice" has created folder "/folderB" + And user "Alice" has shared folder "/folderA" with user "Brian" + And user "Alice" has shared folder "/folderB" with user "Brian" + And user "Brian" has created folder "/folderA/ONE" + And user "Brian" has stored id of folder "/folderA/ONE" + And user "Brian" has created folder "/folderA/ONE/TWO" + When user "Brian" moves folder "/folderA/ONE" to "/folderB/ONE" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/folderA" should exist + And as "Brian" folder "/folderA/ONE" should not exist + # yes, a weird bug used to make this one fail + And as "Brian" folder "/folderA/ONE/TWO" should not exist + And as "Brian" folder "/folderB/ONE" should exist + And as "Brian" folder "/folderB/ONE/TWO" should exist + And user "Brian" folder "/folderB/ONE" should have the previously stored id + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-reva-211 + Scenario Outline: Renaming a file to a path with extension .part should not be possible + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToRename.txt" + When user "Alice" moves file "/fileToRename.txt" to "/welcome.part" using the WebDAV API + Then the HTTP status code should be "400" + And the DAV exception should be "OCA\DAV\Connector\Sabre\Exception\InvalidPath" + And the DAV message should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And the DAV reason should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And user "Alice" should see the following elements + | /fileToRename.txt | + But user "Alice" should not see the following elements + | /fileToRename.part | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @sqliteDB + Scenario Outline: renaming to a file with special characters + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 2" to "textfile2.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 3" to "textfile3.txt" + When user "Alice" moves the following file using the WebDAV API + | source | destination | + | /textfile0.txt | *a@b#c$e%f&g* | + | /textfile1.txt | 1 2 3##.## | + | /textfile2.txt | file[2] | + | /textfile3.txt | file [ 3 ] | + Then the HTTP status code of responses on all endpoints should be "201" + And the content of file "*a@b#c$e%f&g*" for user "Alice" should be "ownCloud test text file 0" + And the content of file "1 2 3##.##" for user "Alice" should be "ownCloud test text file 1" + And the content of file "file[2]" for user "Alice" should be "ownCloud test text file 2" + And the content of file "file [ 3 ]" for user "Alice" should be "ownCloud test text file 3" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-265 + #after fixing the issues merge this Scenario into the one above + Scenario Outline: renaming to a file with question mark in its name + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + When user "Alice" moves file "/textfile0.txt" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | renamed_file | + | old | #oc ab?cd=ef# | + | new | #oc ab?cd=ef# | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | renamed_file | + | spaces | #oc ab?cd=ef# | + + + Scenario Outline: renaming file with dots in the path + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content for file name ending with a dot" to "/" + When user "Alice" moves file "/" to "/abc.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/abc.txt" should exist + Examples: + | dav_version | folder_name | file_name | + | old | /upload. | abc. | + | old | /upload. | abc . | + | old | /upload.1 | abc | + | old | /upload...1.. | abc...txt.. | + | old | /... | abcd.txt | + | old | /..upload | ..abc | + | new | /upload. | abc. | + | new | /upload. | abc . | + | new | /upload.1 | ..abc.txt | + | new | /upload...1.. | abc...txt.. | + | new | /... | ... | + | new | /..upload | ..abc | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /upload. | abc. | + | spaces | /upload. | abc . | + | spaces | /upload.1 | abc | + | spaces | /upload...1.. | abc...txt.. | + | spaces | /... | abcd.txt | + | spaces | /... | ... | + + @smokeTest + Scenario Outline: user tries to move a file that doesnt exist into a folder + Given using DAV path + And user "Alice" has created folder "FOLDER" + When user "Alice" moves file "/doesNotExist.txt" to "/FOLDER/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "404" + And as "Alice" file "/FOLDER/textfile0.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: user tries to rename a file that doesnt exist + Given using DAV path + When user "Alice" moves file "/doesNotExist.txt" to "/exist.txt" using the WebDAV API + Then the HTTP status code should be "404" + And as "Alice" file "/exist.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Moving a hidden file + Given using DAV path + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file101 | + | /FOLDER/.hidden_file102 | + When user "Alice" moves the following files using the WebDAV API + | from | to | + | .hidden_file101 | /FOLDER/.hidden_file101 | + | /FOLDER/.hidden_file102 | .hidden_file102 | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the following files should exist + | path | + | .hidden_file102 | + | /FOLDER/.hidden_file101 | + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file102 | + | /FOLDER/.hidden_file101 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Renaming to/from a hidden file + Given using DAV path + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file101 | + | hidden_file101.txt | + When user "Alice" moves the following files using the WebDAV API + | from | to | + | .hidden_file101 | hidden_file102.txt | + | hidden_file101.txt | .hidden_file102 | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the following files should exist + | path | + | .hidden_file102 | + | hidden_file102.txt | + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file102 | + | hidden_file102.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Moving a file (deep moves with various folder and file names) + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "//" + When user "Alice" moves file "//" to "//" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "//" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | source_folder | source_file | target_folder | target_file | + | old | text | file.txt | 0 | file.txt | + | old | text | file.txt | 1 | file.txt | + | old | 0 | file.txt | text | file.txt | + | old | 1 | file.txt | text | file.txt | + | old | texta | 0 | textb | file.txt | + | old | texta | 1 | textb | file.txt | + | old | texta | file.txt | textb | 0 | + | old | texta | file.txt | textb | 1 | + | new | text | file.txt | 0 | file.txt | + | new | text | file.txt | 1 | file.txt | + | new | 0 | file.txt | text | file.txt | + | new | 1 | file.txt | text | file.txt | + | new | texta | 0 | textb | file.txt | + | new | texta | 1 | textb | file.txt | + | new | texta | file.txt | textb | 0 | + | new | texta | file.txt | textb | 1 | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | source_folder | source_file | target_folder | target_file | + | spaces | text | file.txt | 0 | file.txt | + | spaces | text | file.txt | 1 | file.txt | + | spaces | 0 | file.txt | text | file.txt | + | spaces | 1 | file.txt | text | file.txt | + | spaces | texta | 0 | textb | file.txt | + | spaces | texta | 1 | textb | file.txt | + | spaces | texta | file.txt | textb | 0 | + | spaces | texta | file.txt | textb | 1 | + + + Scenario Outline: Moving a file from a folder to the root + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "//" + When user "Alice" moves file "//" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "/" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | source_folder | source_file | target_file | + | old | 0 | file.txt | file.txt | + | old | 1 | file.txt | file.txt | + | old | texta | 0 | file.txt | + | old | texta | 1 | file.txt | + | old | texta | file.txt | 0 | + | old | texta | file.txt | 1 | + | new | 0 | file.txt | file.txt | + | new | 1 | file.txt | file.txt | + | new | texta | 0 | file.txt | + | new | texta | 1 | file.txt | + | new | texta | file.txt | 0 | + | new | texta | file.txt | 0 | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | source_folder | source_file | target_file | + | spaces | 0 | file.txt | file.txt | + | spaces | 1 | file.txt | file.txt | + | spaces | texta | 0 | file.txt | + | spaces | texta | 1 | file.txt | + | spaces | texta | file.txt | 0 | + | spaces | texta | file.txt | 0 | + + + Scenario Outline: move a file of size zero byte + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" + And user "Alice" has created folder "/testZeroByte" + When user "Alice" moves file "/zerobyte.txt" to "/testZeroByte/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/testZeroByte/zerobyte.txt" should exist + And as "Alice" file "/zerobyte.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a file of size zero byte + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" + When user "Alice" moves file "/zerobyte.txt" to "/rename_zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/rename_zerobyte.txt" should exist + And as "Alice" file "/zerobyte.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature b/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature new file mode 100644 index 00000000000..6aaea8d7573 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove2/moveFileToBlacklistedName.feature @@ -0,0 +1,80 @@ +@api +Feature: users cannot move (rename) a file to a blacklisted name + As an administrator + I want to be able to prevent users from moving (renaming) files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + + @issue-ocis-reva-211 + Scenario Outline: rename a file to a filename that is banned by default + Given using DAV path + When user "Alice" moves file "/textfile0.txt" to "/.htaccess" using the WebDAV API + Then the HTTP status code should be "403" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a file to a banned filename + Given using DAV path + And the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" moves file "/textfile0.txt" to "/blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a file to a filename that matches (or not) blacklisted_files_regex + Given using DAV path + And user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + And the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" moves file "/textfile0.txt" to these filenames using the webDAV API then the results should be as listed + | filename | http-code | exists | + | .ext | 403 | no | + | filename.ext | 403 | no | + | bannedfilename.txt | 403 | no | + | containsbannedstring | 403 | no | + | this-ContainsBannedString.txt | 403 | no | + | /FOLDER/.ext | 403 | no | + | /FOLDER/filename.ext | 403 | no | + | /FOLDER/bannedfilename.txt | 403 | no | + | /FOLDER/containsbannedstring | 403 | no | + | /FOLDER/this-ContainsBannedString.txt | 403 | no | + | .extension | 201 | yes | + | filename.txt | 201 | yes | + | bannedfilename | 201 | yes | + | bannedfilenamewithoutdot | 201 | yes | + | not-contains-banned-string.txt | 201 | yes | + | /FOLDER/.extension | 201 | yes | + | /FOLDER/filename.txt | 201 | yes | + | /FOLDER/bannedfilename | 201 | yes | + | /FOLDER/bannedfilenamewithoutdot | 201 | yes | + | /FOLDER/not-contains-banned-string.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature b/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature new file mode 100644 index 00000000000..05e848f39e6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove2/moveFileToExcludedDirectory.feature @@ -0,0 +1,84 @@ +@api @issue-ocis-reva-14 +Feature: users cannot move (rename) a file to or into an excluded directory + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to rename an existing file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + + + Scenario Outline: rename a file to an excluded directory name + Given using DAV path + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves file "/textfile0.txt" to "/.github" using the WebDAV API + Then the HTTP status code should be "403" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a file to an excluded directory name inside a parent directory + Given using DAV path + And user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" moves file "/textfile0.txt" to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: rename a file to a filename that matches (or not) excluded_directories_regex + Given using DAV path + And user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + And the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" moves file "/textfile0.txt" to these filenames using the webDAV API then the results should be as listed + | filename | http-code | exists | + | endswith.bad | 403 | no | + | thisendswith.bad | 403 | no | + | .git | 403 | no | + | .github | 403 | no | + | containsvirusinthename | 403 | no | + | this-containsvirusinthename.txt | 403 | no | + | /FOLDER/endswith.bad | 403 | no | + | /FOLDER/thisendswith.bad | 403 | no | + | /FOLDER/.git | 403 | no | + | /FOLDER/.github | 403 | no | + | /FOLDER/containsvirusinthename | 403 | no | + | /FOLDER/this-containsvirusinthename.txt | 403 | no | + | endswith.badandotherstuff | 201 | yes | + | thisendswith.badandotherstuff | 201 | yes | + | name.git | 201 | yes | + | name.github | 201 | yes | + | not-contains-virus-in-the-name.txt | 201 | yes | + | /FOLDER/endswith.badandotherstuff | 201 | yes | + | /FOLDER/thisendswith.badandotherstuff | 201 | yes | + | /FOLDER/name.git | 201 | yes | + | /FOLDER/name.github | 201 | yes | + | /FOLDER/not-contains-virus-in-the-name.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature b/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature new file mode 100644 index 00000000000..9e5f929f6d6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature @@ -0,0 +1,222 @@ +@api @files_sharing-app-required @skipOnOcV10 +Feature: move (rename) file + As a user + I want to be able to move and rename files + So that I can manage my file system + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "" has uploaded file with content "test data" to "/testfile.txt" + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "" moves file "/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/Shares/testshare/testfile.txt" for user "Alice" should be "test data" + And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data" + And as "" file "/testfile.txt" should not exist + Examples: + | dav_version | mover | destination_folder | + | old | Alice | /Shares/testshare | + | old | Brian | /testshare | + | new | Alice | /Shares/testshare | + | new | Brian | /testshare | + + + Scenario Outline: Moving a file out of a shared folder as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "test data" to "/testshare/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Brian" moves file "/testshare/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testfile.txt" for user "Brian" should be "test data" + And as "Alice" file "/Shares/testshare/testfile.txt" should not exist + And as "Brian" file "/testshare/testfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Can not move a file out of a shared folder as the sharee + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "test data" to "/testshare/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" moves file "/Shares/testshare/testfile.txt" to "/testfile.txt" using the WebDAV API + Then the HTTP status code should be "502" + And as "Alice" file "/Shares/testshare/testfile.txt" should exist + And as "Brian" file "/testshare/testfile.txt" should exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Moving a folder into a shared folder as the sharee and as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "" has created folder "/testsubfolder" + And user "" has uploaded file with content "test data" to "/testsubfolder/testfile.txt" + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "" moves folder "/testsubfolder" to "/testsubfolder" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/Shares/testshare/testsubfolder/testfile.txt" for user "Alice" should be "test data" + And the content of file "/testshare/testsubfolder/testfile.txt" for user "Brian" should be "test data" + And as "" file "/testsubfolder" should not exist + Examples: + | dav_version | mover | destination_folder | + | old | Alice | /Shares/testshare | + | old | Brian | /testshare | + | new | Alice | /Shares/testshare | + | new | Brian | /testshare | + + + Scenario Outline: Moving a folder out of a shared folder as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created the following folders + | path | + | /testshare | + | /testshare/testsubfolder | + And user "Brian" has uploaded file with content "test data" to "/testshare/testsubfolder/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Brian" moves folder "/testshare/testsubfolder" to "/testsubfolder" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/testsubfolder/testfile.txt" for user "Brian" should be "test data" + And as "Alice" folder "/testshare/testsubfolder" should not exist + And as "Brian" folder "/testshare/testsubfolder" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Moving a folder out of a shared folder as the sharee + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created the following folders + | path | + | /testshare | + | /testshare/testsubfolder | + And user "Brian" has uploaded file with content "test data" to "/testshare/testsubfolder/testfile.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" moves folder "/Shares/testshare/testsubfolder" to "/testsubfolder" using the WebDAV API + Then the HTTP status code should be "502" + And as "Alice" folder "/Shares/testshare/testsubfolder" should exist + And as "Brian" folder "/testshare/testsubfolder" should exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Moving a file to a shared folder with no permissions + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" moves file "/textfile0.txt" to "/Shares/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should not be able to download file "/Shares/testshare/textfile0.txt" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Moving a file to overwrite a file in a shared folder with no permissions + Given using DAV path + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "Welcome to ownCloud" to "fileToCopy.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Brian" has copied file "/fileToCopy.txt" to "/testshare/overwritethis.txt" + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" moves file "/textfile0.txt" to "/Shares/testshare/overwritethis.txt" using the WebDAV API + Then the HTTP status code should be "403" + And the content of file "/Shares/testshare/overwritethis.txt" for user "Alice" should be "Welcome to ownCloud" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Checking file id after a move between received shares + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created the following folders + | path | + | /folderA | + | /folderB | + And user "Alice" has shared folder "/folderA" with user "Brian" + And user "Alice" has shared folder "/folderB" with user "Brian" + And user "Brian" has accepted share "/folderA" offered by user "Alice" + And user "Brian" has accepted share "/folderB" offered by user "Alice" + And user "Brian" has created the following folders + | path | + | /Shares/folderA/ONE | + | /Shares/folderA/ONE/TWO | + And user "Brian" has stored id of folder "/Shares/folderA/ONE" + When user "Brian" moves folder "/Shares/folderA/ONE" to "/Shares/folderB/ONE" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "/Shares/folderA" should exist + And as "Brian" folder "/Shares/folderA/ONE" should not exist + And as "Brian" folder "/Shares/folderA/ONE/TWO" should not exist + And as "Brian" folder "/Shares/folderB/ONE" should exist + And as "Brian" folder "/Shares/folderB/ONE/TWO" should exist + And user "Brian" folder "/Shares/folderB/ONE" should have the previously stored id + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature new file mode 100644 index 00000000000..76e436229a8 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature @@ -0,0 +1,342 @@ +@api +Feature: download file + As a user + I want to be able to download files + So that I can work wih local copies of files on my client system + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has uploaded file with content "Welcome this is just an example file for developers." to "/welcome.txt" + + @smokeTest + Scenario Outline: download a file + Given using DAV path + When user "Alice" downloads file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded content should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-12 + Scenario Outline: download a file with range + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=24-50" using the WebDAV API + Then the HTTP status code should be "206" + And the downloaded content should be "example file for developers" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download a file larger than 4MB (ref: https://github.com/sabre-io/http/pull/119 ) + Given using DAV path + And user "Alice" has uploaded file "/file9000000.txt" ending with "text at end of file" of size 9000000 bytes + When user "Alice" downloads file "/file9000000.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the size of the downloaded file should be 9000000 bytes + And the downloaded content should end with "text at end of file" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @notToImplementOnOCIS + Scenario Outline: Downloading a file should serve security headers + Given using DAV path + When user "Alice" downloads file "/welcome.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt" | + | Content-Security-Policy | default-src 'none'; | + | X-Content-Type-Options | nosniff | + | X-Download-Options | noopen | + | X-Frame-Options | SAMEORIGIN | + | X-Permitted-Cross-Domain-Policies | none | + | X-Robots-Tag | none | + | X-XSS-Protection | 0 | + And the downloaded content should start with "Welcome" + Examples: + | dav_version | + | old | + | new | + + @notToImplementOnOCIS + Scenario Outline: Doing a GET with a web login should work without CSRF token on the new backend + Given using DAV path + And user "Alice" has logged in to a web-style session + When the client sends a "GET" to "/remote.php/dav/files/%username%/welcome.txt" of user "Alice" without requesttoken + Then the HTTP status code should be "200" + And the downloaded content should start with "Welcome" + Examples: + | dav_version | + | old | + | new | + + @notToImplementOnOCIS + Scenario Outline: Doing a GET with a web login should work with CSRF token on the new backend + Given using DAV path + And user "Alice" has logged in to a web-style session + When the client sends a "GET" to "/remote.php/dav/files/%username%/welcome.txt" of user "Alice" with requesttoken + Then the HTTP status code should be "200" + And the downloaded content should start with "Welcome" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Get the size of a file + Given using DAV path + And user "Alice" has uploaded file with content "This is a test file" to "test-file.txt" + When user "Alice" gets the size of file "test-file.txt" using the WebDAV API + Then the HTTP status code should be "207" + And the size of the file should be "19" + + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-98 + Scenario Outline: Get the content-length response header of a pdf file + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/simple.pdf" to "/simple.pdf" + When user "Alice" downloads file "/simple.pdf" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Length | 9622 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-98 + Scenario Outline: Get the content-length response header of an image file + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/testavatar.png" to "/testavatar.png" + When user "Alice" downloads file "/testavatar.png" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Length | 35323 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Download a file with comma in the filename + Given using DAV path + And user "Alice" has uploaded file with content "file with comma in filename" to + When user "Alice" downloads file using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded content should be "file with comma in filename" + Examples: + | dav_version | filename | + | old | "sample,1.txt" | + | old | ",,,.txt" | + | old | ",,,.," | + | new | "sample,1.txt" | + | new | ",,,.txt" | + | new | ",,,.," | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | filename | + | spaces | "sample,1.txt" | + | spaces | ",,,.txt" | + | spaces | ",,,.," | + + + Scenario Outline: download a file with single part ranges + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=0-51" using the WebDAV API + Then the HTTP status code should be "206" + And the following headers should be set + | header | value | + | Content-Length | 52 | + | Content-Range | bytes 0-51/52 | + And the downloaded content should be "Welcome this is just an example file for developers." + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download a file with multipart ranges + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=0-6, 40-51" using the WebDAV API + Then the HTTP status code should be "206" or "200" + And if the HTTP status code was "206" then the following headers should match these regular expressions + | Content-Length | /\d+/ | + | Content-Type | /^multipart\/byteranges; boundary=[a-zA-Z0-9_.-]*$/ | + And if the HTTP status code was "206" then the downloaded content for multipart byterange should be: + """ + Content-type: text/plain;charset=UTF-8 + Content-range: bytes 0-6/52 + + Welcome + + Content-type: text/plain;charset=UTF-8 + Content-range: bytes 40-51/52 + + developers. + """ + But if the HTTP status code was "200" then the downloaded content should be "Welcome this is just an example file for developers." + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download a file with last byte range out of bounds + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=0-55" using the WebDAV API + Then the HTTP status code should be "206" + And the downloaded content should be "Welcome this is just an example file for developers." + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download a range at the end of a file + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=-11" using the WebDAV API + Then the HTTP status code should be "206" + And the downloaded content should be "developers." + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download a file with range out of bounds + Given using DAV path + When user "Alice" downloads file "/welcome.txt" with range "bytes=55-60" using the WebDAV API + Then the HTTP status code should be "416" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: download hidden files + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + When user "Alice" downloads the following files using the WebDAV API + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Then the HTTP status code of responses on all endpoints should be "200" + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest @skipOnOcV10 + Scenario Outline: Downloading a file should serve security headers + Given using DAV path + When user "Alice" downloads file "/welcome.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt" | + | Content-Security-Policy | default-src 'none'; | + | X-Content-Type-Options | nosniff | + | X-Download-Options | noopen | + | X-Frame-Options | SAMEORIGIN | + | X-Permitted-Cross-Domain-Policies | none | + | X-Robots-Tag | none | + | X-XSS-Protection | 1; mode=block | + And the downloaded content should start with "Welcome" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario: download a zero byte size file + Given user "Alice" has uploaded file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" + When user "Alice" downloads file "/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the size of the downloaded file should be 0 bytes \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature b/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature new file mode 100644 index 00000000000..b91ec668af3 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature @@ -0,0 +1,433 @@ +@api +Feature: list files + As a user + I want to be able to list my files and folders (resources) + So that I can understand my file structure in owncloud + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created the following folders + | path | + | simple-folder | + | simple-folder/simple-folder1 | + | simple-folder/simple-empty-folder | + | simple-folder/simple-folder1/simple-folder2 | + And user "Alice" has uploaded the following files with content "simple-test-content" + | path | + | textfile0.txt | + | welcome.txt | + | simple-folder/textfile0.txt | + | simple-folder/welcome.txt | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + + + Scenario Outline: Get the list of resources in the root folder with depth 0 + Given using DAV path + When user "Alice" lists the resources in "/" with depth "0" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should not contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + | simple-folder/welcome.txt | + | simple-folder/textfile0.txt | + | simple-folder/simple-empty-folder | + | simple-folder/simple-folder1 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get the list of resources in the root folder with depth 1 + Given using DAV path + When user "Alice" lists the resources in "/" with depth "1" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + And the last DAV response for user "Alice" should not contain these nodes + | name | + | simple-folder/welcome.txt | + | simple-folder/textfile0.txt | + | simple-folder/simple-empty-folder | + | simple-folder/simple-folder1 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindEnabled + Scenario Outline: Get the list of resources in the root folder with depth infinity + Given using DAV path + And the administrator has set depth_infinity_allowed to 1 + When user "Alice" lists the resources in "/" with depth "infinity" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + | simple-folder/textfile0.txt | + | simple-folder/welcome.txt | + | simple-folder/simple-empty-folder/ | + | simple-folder/simple-folder1/ | + | simple-folder/simple-folder1/simple-folder2 | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get the list of resources in a folder with depth 0 + Given using DAV path + When user "Alice" lists the resources in "/simple-folder" with depth "0" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should contain these nodes + | name | + | simple-folder/ | + And the last DAV response for user "Alice" should not contain these nodes + | name | + | simple-folder/welcome.txt | + | simple-folder/textfile0.txt | + | simple-folder/simple-empty-folder | + | simple-folder/simple-folder1 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get the list of resources in a folder with depth 1 + Given using DAV path + When user "Alice" lists the resources in "/simple-folder" with depth "1" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should contain these nodes + | name | + | simple-folder/welcome.txt | + | simple-folder/textfile0.txt | + | simple-folder/simple-empty-folder | + | simple-folder/simple-folder1 | + And the last DAV response for user "Alice" should not contain these nodes + | name | + | simple-folder/simple-folder1/simple-folder2 | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindEnabled + Scenario Outline: Get the list of resources in a folder with depth infinity + Given using DAV path + And the administrator has set depth_infinity_allowed to 1 + When user "Alice" lists the resources in "/simple-folder" with depth "infinity" using the WebDAV API + Then the HTTP status code should be "207" + And the last DAV response for user "Alice" should contain these nodes + | name | + | /simple-folder/textfile0.txt | + | /simple-folder/welcome.txt | + | /simple-folder/simple-folder1/ | + | simple-folder/simple-folder1/simple-folder2 | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get the list of resources in a folder shared through public link with depth 0 + Given using DAV path + And user "Alice" has created the following folders + | path | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + And user "Alice" has created a public link share of folder "simple-folder" + When the public lists the resources in the last created public link with depth "0" using the WebDAV API + Then the HTTP status code should be "207" + And the last public link DAV response should not contain these nodes + | name | + | /textfile0.txt | + | /welcome.txt | + | /simple-folder1/ | + | /simple-folder1/welcome.txt | + | /simple-folder1/simple-folder2 | + | /simple-folder1/textfile0.txt | + | /simple-folder1/simple-folder2/textfile0.txt | + | /simple-folder1/simple-folder2/welcome.txt | + | /simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | dav_version | + | old | + + Examples: + | dav_version | + | new | + + + Scenario Outline: Get the list of resources in a folder shared through public link with depth 1 + Given using DAV path + And user "Alice" has created the following folders + | path | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + And user "Alice" has created a public link share of folder "simple-folder" + When the public lists the resources in the last created public link with depth "1" using the WebDAV API + Then the HTTP status code should be "207" + And the last public link DAV response should contain these nodes + | name | + | /textfile0.txt | + | /welcome.txt | + | /simple-folder1/ | + And the last public link DAV response should not contain these nodes + | name | + | /simple-folder1/simple-folder2/textfile0.txt | + | /simple-folder1/simple-folder2/welcome.txt | + | /simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder1/welcome.txt | + | /simple-folder1/simple-folder2 | + | /simple-folder1/textfile0.txt | + | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | dav_version | + | old | + + Examples: + | dav_version | + | new | + + @depthInfinityPropfindEnabled + Scenario Outline: Get the list of resources in a folder shared through public link with depth infinity + Given using DAV path + And the administrator has set depth_infinity_allowed to 1 + And user "Alice" has created the following folders + | path | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + And user "Alice" has created a public link share of folder "simple-folder" + When the public lists the resources in the last created public link with depth "infinity" using the WebDAV API + Then the HTTP status code should be "207" + And the last public link DAV response should contain these nodes + | name | + | /textfile0.txt | + | /welcome.txt | + | /simple-folder1/ | + | /simple-folder1/welcome.txt | + | /simple-folder1/simple-folder2 | + | /simple-folder1/textfile0.txt | + | /simple-folder1/simple-folder2/textfile0.txt | + | /simple-folder1/simple-folder2/welcome.txt | + | /simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | dav_version | + | old | + + Examples: + | dav_version | + | new | + + + Scenario Outline: Get the list of files in the trashbin with depth 0 + Given using DAV path + And user "Alice" has deleted the following resources + | path | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + When user "Alice" lists the resources in the trashbin with depth "0" using the WebDAV API + Then the HTTP status code should be "207" + And the trashbin DAV response should not contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + | simple-folder/textfile0.txt | + | simple-folder/welcome.txt | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Get the list of files in the trashbin with depth 1 + Given using DAV path + And user "Alice" has deleted the following resources + | path | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + When user "Alice" lists the resources in the trashbin with depth "1" using the WebDAV API + Then the HTTP status code should be "207" + And the trashbin DAV response should contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + And the trashbin DAV response should not contain these nodes + | name | + | simple-folder/textfile0.txt | + | simple-folder/welcome.txt | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindEnabled + Scenario Outline: Get the list of files in the trashbin with depth infinity + Given using DAV path + And the administrator has set depth_infinity_allowed to 1 + And user "Alice" has deleted the following resources + | path | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + When user "Alice" lists the resources in the trashbin with depth "infinity" using the WebDAV API + Then the HTTP status code should be "207" + And the trashbin DAV response should contain these nodes + | name | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + | simple-folder/textfile0.txt | + | simple-folder/welcome.txt | + | simple-folder/simple-folder1/textfile0.txt | + | simple-folder/simple-folder1/welcome.txt | + | simple-folder/simple-folder1/simple-folder2/textfile0.txt | + | simple-folder/simple-folder1/simple-folder2/welcome.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindDisabled + Scenario Outline: Get the list of resources in the root folder with depth infinity when depth infinity is not allowed + Given using DAV path + When user "Alice" lists the resources in "/" with depth "infinity" using the WebDAV API + Then the HTTP status code should be "412" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindDisabled + Scenario Outline: Get the list of resources in a folder shared through public link with depth infinity when depth infinity is not allowed + Given using DAV path + And user "Alice" has created the following folders + | path | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3 | + | /simple-folder/simple-folder1/simple-folder2/simple-folder3/simple-folder4 | + And user "Alice" has created a public link share of folder "simple-folder" + When the public lists the resources in the last created public link with depth "infinity" using the WebDAV API + Then the HTTP status code should be "412" + @notToImplementOnOCIS @issue-ocis-2079 + Examples: + | dav_version | + | old | + + Examples: + | dav_version | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @depthInfinityPropfindDisabled + Scenario Outline: Get the list of files in the trashbin with depth infinity when depth infinity is not allowed + Given using DAV path + And user "Alice" has deleted the following resources + | path | + | textfile0.txt | + | welcome.txt | + | simple-folder/ | + When user "Alice" lists the resources in the trashbin with depth "infinity" using the WebDAV API + Then the HTTP status code should be "412" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavOperations/propfind.feature b/tests/acceptance/features/coreApiWebdavOperations/propfind.feature new file mode 100644 index 00000000000..7994cdcf58d --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavOperations/propfind.feature @@ -0,0 +1,50 @@ +@api +Feature: PROPFIND + + @issue-ocis-751 + Scenario Outline: PROPFIND to "/remote.php/dav/(files|spaces)" + Given user "Alice" has been created with default attributes and without skeleton files + When user "Alice" requests "" with "PROPFIND" using basic auth + Then the HTTP status code should be "405" + Examples: + | dav_path | + | /remote.php/dav/files | + + @skipOnOcV10 @personalSpace + Examples: + | dav_path | + | /remote.php/dav/spaces | + + + Scenario Outline: PROPFIND to "/remote.php/dav/(files|spaces)" with depth header + Given user "Alice" has been created with default attributes and without skeleton files + And the administrator has set depth_infinity_allowed to + When user "Alice" requests "" with "PROPFIND" using basic auth and with headers + | header | value | + | depth | | + Then the HTTP status code should be "" + @notToImplementOnOCIS @depthInfinityPropfindEnabled + Examples: + | dav_path | depth_infinity_allowed | depth | http_status | + | /remote.php/dav/files/alice | 1 | 0 | 207 | + | /remote.php/dav/files/alice | 1 | infinity | 207 | + @notToImplementOnOCIS @depthInfinityPropfindDisabled + Examples: + | dav_path | depth_infinity_allowed | depth | http_status | + | /remote.php/dav/files/alice | 0 | 0 | 207 | + | /remote.php/dav/files/alice | 0 | infinity | 412 | + @skipOnOcV10 @depthInfinityPropfindEnabled + Examples: + | dav_path | depth_infinity_allowed | depth | http_status | + | /remote.php/dav/files/alice | 1 | 0 | 207 | + | /remote.php/dav/files/alice | 1 | infinity | 207 | + @skipOnOcV10 @personalSpace @depthInfinityPropfindDisabled + Examples: + | dav_path | depth_infinity_allowed | depth | http_status | + | /remote.php/dav/spaces/%spaceid% | 0 | 0 | 207 | + | /remote.php/dav/spaces/%spaceid% | 0 | infinity | 207 | + @skipOnOcV10 @personalSpace @depthInfinityPropfindEnabled + Examples: + | dav_path | depth_infinity_allowed | depth | http_status | + | /remote.php/dav/spaces/%spaceid% | 1 | 0 | 207 | + | /remote.php/dav/spaces/%spaceid% | 1 | infinity | 207 | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature b/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature new file mode 100644 index 00000000000..cb51ce29abe --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature @@ -0,0 +1,41 @@ +@api +Feature: refuse access + As an administrator + I want to refuse access to unauthenticated and disabled users + So that I can secure the system + + Background: + Given using OCS API version "1" + + @smokeTest + Scenario Outline: Unauthenticated call + # cannot perform with spaces WebDAV due to the absence of user + Given using DAV path + When an unauthenticated client connects to the DAV endpoint using the WebDAV API + Then the HTTP status code should be "401" + And there should be no duplicate headers + And the following headers should be set + | header | value | + | WWW-Authenticate | Basic realm="%productname%", charset="UTF-8" | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: A disabled user cannot use webdav + Given using DAV path + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has been disabled + When user "Alice" downloads file "/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "401" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavOperations/search.feature b/tests/acceptance/features/coreApiWebdavOperations/search.feature new file mode 100644 index 00000000000..d9ffa46766b --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavOperations/search.feature @@ -0,0 +1,337 @@ +@api @issue-ocis-reva-39 +Feature: Search + As a user + I would like to be able to search for files + So that I can find needed files quickly + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/just-a-folder" + And user "Alice" has created folder "/फनी näme" + And user "Alice" has created folder "/upload folder" + And user "Alice" has created folder "/upload😀 😁" + And user "Alice" has uploaded file with content "does-not-matter" to "/upload.txt" + And user "Alice" has uploaded file with content "does-not-matter" to "/a-image.png" + And user "Alice" has uploaded file with content "does-not-matter" to "/just-a-folder/upload.txt" + And user "Alice" has uploaded file with content "does-not-matter" to "/just-a-folder/lolol.txt" + And user "Alice" has uploaded file with content "does-not-matter" to "/just-a-folder/a-image.png" + And user "Alice" has uploaded file with content "does-not-matter" to "/just-a-folder/uploadÜठिF.txt" + And user "Alice" has uploaded file with content "does-not-matter" to "/फनी näme/upload.txt" + And user "Alice" has uploaded file with content "does-not-matter" to "/फनी näme/a-image.png" + And user "Alice" has uploaded file with content "does-not-matter" to "/upload😀 😁/upload😀 😁.txt" + And user "Alice" has uploaded file with content "file with comma in filename" to "/upload😀 😁/upload,1.txt" + + @smokeTest + Scenario Outline: search for entry by pattern + Given using DAV path + When user "Alice" searches for "upload" using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain these entries: + | /upload.txt | + | /just-a-folder/upload.txt | + | /upload folder | + | /just-a-folder/uploadÜठिF.txt | + | /फनी näme/upload.txt | + | /upload😀 😁 | + | /upload😀 😁/upload😀 😁.txt | + | /upload😀 😁/upload,1.txt | + But the search result of user "Alice" should not contain these entries: + | /a-image.png | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: search for entries by only some letters from the middle of the entry name + Given using DAV path + And user "Alice" has created folder "FOLDER" + When user "Alice" searches for "ol" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "4" entries + And the search result of user "Alice" should contain these entries: + | /just-a-folder | + | /upload folder | + | /FOLDER | + | /just-a-folder/lolol.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: search for files by extension + Given using DAV path + When user "Alice" searches for "png" using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain these entries: + | /a-image.png | + | /just-a-folder/a-image.png | + | /फनी näme/a-image.png | + But the search result of user "Alice" should not contain these entries: + | /upload.txt | + | /just-a-folder/upload.txt | + | /just-a-folder/uploadÜठिF.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: search with empty field + Given using DAV path + When user "Alice" searches for "" using the WebDAV API + Then the HTTP status code should be "400" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: limit returned search entries + Given using DAV path + When user "Alice" searches for "upload" and limits the results to "3" items using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain any "3" of these entries: + | /just-a-folder/upload.txt | + | /just-a-folder/uploadÜठिF.txt | + | /upload folder | + | /upload.txt | + | /फनी näme/upload.txt | + | /upload😀 😁 | + | /upload😀 😁/upload😀 😁.txt | + | /upload😀 😁/upload,1.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: limit returned search entries to only 1 entry + Given using DAV path + When user "Alice" searches for "upload" and limits the results to "1" items using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain any "1" of these entries: + | /just-a-folder/upload.txt | + | /just-a-folder/uploadÜठिF.txt | + | /upload folder | + | /upload.txt | + | /फनी näme/upload.txt | + | /upload😀 😁 | + | /upload😀 😁/upload😀 😁.txt | + | /upload😀 😁/upload,1.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: limit returned search entries to more entires than there are + Given using DAV path + When user "Alice" searches for "upload" and limits the results to "100" items using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "8" entries + And the search result of user "Alice" should contain these entries: + | /upload.txt | + | /just-a-folder/upload.txt | + | /upload folder | + | /just-a-folder/uploadÜठिF.txt | + | /फनी näme/upload.txt | + | /upload😀 😁 | + | /upload😀 😁/upload😀 😁.txt | + | /upload😀 😁/upload,1.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-4712 + Scenario Outline: report extra properties in search entries for a file + Given using DAV path + When user "Alice" searches for "upload" using the WebDAV API requesting these properties: + | oc:fileid | + | oc:permissions | + | a:getlastmodified | + | a:getetag | + | a:getcontenttype | + | oc:size | + | oc:owner-id | + | oc:owner-display-name | + Then the HTTP status code should be "207" + And file "/upload.txt" in the search result of user "Alice" should contain these properties: + | name | value | + | {http://owncloud.org/ns}fileid | \d* | + | {http://owncloud.org/ns}permissions | ^(RDNVW\|RMDNVW)$ | + | {DAV:}getlastmodified | ^[MTWFS][uedhfriatno]{2},\s(\d){2}\s[JFMAJSOND][anebrpyulgctov]{2}\s\d{4}\s\d{2}:\d{2}:\d{2} GMT$ | + | {DAV:}getetag | ^\"[a-f0-9:\.]{1,32}\"$ | + | {DAV:}getcontenttype | text\/plain | + | {http://owncloud.org/ns}size | 15 | + | {http://owncloud.org/ns}owner-id | %username% | + | {http://owncloud.org/ns}owner-display-name | %displayname% | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-4712 + Scenario Outline: report extra properties in search entries for a folder + Given using DAV path + When user "Alice" searches for "upload" using the WebDAV API requesting these properties: + | oc:fileid | + | oc:permissions | + | a:getlastmodified | + | a:getetag | + | a:getcontenttype | + | oc:size | + | oc:owner-id | + | oc:owner-display-name | + Then the HTTP status code should be "207" + And folder "/upload folder" in the search result of user "Alice" should contain these properties: + | name | value | + | {http://owncloud.org/ns}fileid | \d* | + | {http://owncloud.org/ns}permissions | ^(RDNVCK\|RMDNVCK)$ | + | {DAV:}getlastmodified | ^[MTWFS][uedhfriatno]{2},\s(\d){2}\s[JFMAJSOND][anebrpyulgctov]{2}\s\d{4}\s\d{2}:\d{2}:\d{2} GMT$ | + | {DAV:}getetag | ^\"[a-f0-9:\.]{1,32}\"$ | + | {http://owncloud.org/ns}size | 0 | + | {http://owncloud.org/ns}owner-id | %username% | + | {http://owncloud.org/ns}owner-display-name | %displayname% | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: search for entry with emoji by pattern + Given using DAV path + When user "Alice" searches for "😀 😁" using the WebDAV API + Then the HTTP status code should be "207" + And the search result of user "Alice" should contain these entries: + | /upload😀 😁 | + | /upload😀 😁/upload😀 😁.txt | + But the search result of user "Alice" should not contain these entries: + | /a-image.png | + | /upload.txt | + | /just-a-folder/upload.txt | + | /upload folder | + | /just-a-folder/uploadÜठिF.txt | + | /फनी näme/upload.txt | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario: search for entry by tags using REPORT method + Given user "Alice" has created a "normal" tag with name "JustARegularTag1" + And user "Alice" has created a "normal" tag with name "JustARegularTag2" + And user "Alice" has added tag "JustARegularTag1" to folder "फनी näme" + And user "Alice" has added tag "JustARegularTag1" to file "upload.txt" + And user "Alice" has added tag "JustARegularTag2" to file "upload.txt" + When user "Alice" searches for resources tagged with tag "JustARegularTag1" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Alice" should contain these entries: + | फनी näme | + | upload.txt | + When user "Alice" searches for resources tagged with tag "JustARegularTag2" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Alice" should contain these entries: + | upload.txt | + + + Scenario: share a tagged resource to another internal user and sharee searches for tag using REPORT method + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created a "normal" tag with name "JustARegularTag1" + And user "Alice" has created a "normal" tag with name "JustARegularTag2" + And user "Alice" has added tag "JustARegularTag1" to folder "फनी näme" + And user "Alice" has added tag "JustARegularTag1" to file "upload.txt" + And user "Alice" has added tag "JustARegularTag2" to file "upload.txt" + And user "Alice" has shared file "फनी näme" with user "Brian" + And user "Alice" has shared file "upload.txt" with user "Brian" + When user "Brian" searches for resources tagged with tag "JustARegularTag1" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Brian" should contain these entries: + | फनी näme | + | upload.txt | + When user "Brian" searches for resources tagged with tag "JustARegularTag2" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Brian" should contain these entries: + | upload.txt | + When user "Brian" searches for resources tagged with all of the following tags using the webDAV API + | JustARegularTag1 | + | JustARegularTag2 | + Then the HTTP status code should be "207" + And as user "Brian" the response should contain file "upload.txt" + And as user "Brian" the response should not contain file "फनी näme" + + + Scenario: search for entries across various folders by tags using REPORT method + Given user "Alice" has created folder "/just-a-folder/inner-folder" + And user "Alice" has uploaded file with content "inner file" to "/just-a-folder/inner-folder/upload.txt" + And user "Alice" has created a "normal" tag with name "JustARegularTag1" + And user "Alice" has created a "normal" tag with name "JustARegularTag2" + And user "Alice" has added tag "JustARegularTag1" to folder "/just-a-folder/upload.txt" + And user "Alice" has added tag "JustARegularTag1" to file "/फनी näme/upload.txt" + And user "Alice" has added tag "JustARegularTag1" to file "/just-a-folder/inner-folder/upload.txt" + And user "Alice" has added tag "JustARegularTag2" to file "/upload😀 😁/upload,1.txt" + And user "Alice" has added tag "JustARegularTag2" to file "/upload.txt" + When user "Alice" searches for resources tagged with tag "JustARegularTag1" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Alice" should contain these entries: + | upload.txt | + | upload.txt | + | upload.txt | + When user "Alice" searches for resources tagged with tag "JustARegularTag2" using the webDAV API + Then the HTTP status code should be "207" + And the search result by tags for user "Alice" should contain these entries: + | upload,1.txt | + | upload.txt | diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previews.feature b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature new file mode 100644 index 00000000000..b038397f8ae --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavPreviews/previews.feature @@ -0,0 +1,278 @@ +@api @preview-extension-required +Feature: previews of files downloaded through the webdav API + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: download previews with invalid width + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width "" and height "32" using the WebDAV API + Then the HTTP status code should be "400" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "Cannot set width of 0 or smaller!" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\BadRequest" + Examples: + | width | + | 0 | + | 0.5 | + | -1 | + | false | + | true | + | A | + | %2F | + + + Scenario Outline: download previews with invalid height + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width "32" and height "" using the WebDAV API + Then the HTTP status code should be "400" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "Cannot set height of 0 or smaller!" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\BadRequest" + Examples: + | height | + | 0 | + | 0.5 | + | -1 | + | false | + | true | + | A | + | %2F | + + + Scenario: download previews of files inside sub-folders + Given user "Alice" has created folder "subfolder" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/subfolder/parent.txt" + When user "Alice" downloads the preview of "/subfolder/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + + + Scenario Outline: download previews of file types that don't support preview + Given user "Alice" has uploaded file "filesForUpload/" to "/" + When user "Alice" downloads the preview of "/" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + Examples: + | filename | newfilename | + | simple.pdf | test.pdf | + | simple.odt | test.odt | + | new-data.zip | test.zip | + + + Scenario Outline: download previews of different image file types + Given user "Alice" has uploaded file "filesForUpload/" to "/" + When user "Alice" downloads the preview of "/" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + Examples: + | imageName | newImageName | + | testavatar.jpg | testimage.jpg | + | testavatar.png | testimage.png | + + + Scenario: download previews of image after renaming it + Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testimage.jpg" + And user "Alice" has moved file "/testimage.jpg" to "/testimage.txt" + When user "Alice" downloads the preview of "/testimage.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + + + Scenario: download previews of shared files (to shares folder) + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And user "Alice" has shared file "/parent.txt" with user "Brian" + And user "Brian" has accepted share "/parent.txt" offered by user "Alice" + When user "Brian" downloads the preview of "/Shares/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + + @notToImplementOnOCIS + Scenario: download previews of shared files (to root) + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And user "Alice" has shared file "/parent.txt" with user "Brian" + When user "Brian" downloads the preview of "/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + + + Scenario: download previews of other users files + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Brian" downloads the preview of "/parent.txt" of "Alice" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "File not found: parent.txt in '%username%'" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + + Scenario: download previews of folders + Given user "Alice" has created folder "subfolder" + When user "Alice" downloads the preview of "/subfolder/" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "400" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "Unsupported file type" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\BadRequest" + + + Scenario: download previews of not-existing files + When user "Alice" downloads the preview of "/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "File with name parent.txt could not be located" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + + Scenario: Download file previews when it is disabled by the administrator + Given the administrator has updated system config key "enable_previews" with value "false" and type "boolean" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + + Scenario: unset maximum size of previews + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And the administrator has updated system config key "preview_max_x" with value "null" + And the administrator has updated system config key "preview_max_y" with value "null" + When user "Alice" downloads the preview of "/parent.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + + Scenario: download preview of size "null" + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And the administrator has updated system config key "preview_max_x" with value "null" + And the administrator has updated system config key "preview_max_y" with value "null" + When user "Alice" downloads the preview of "/parent.txt" with width "null" and height "null" using the WebDAV API + Then the HTTP status code should be "400" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\BadRequest" + + + Scenario Outline: download previews of different size smaller than the maximum size set + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And the administrator has updated system config key "preview_max_x" with value "32" + And the administrator has updated system config key "preview_max_y" with value "32" + When user "Alice" downloads the preview of "/parent.txt" with width "" and height "" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "" pixels wide and "" pixels high + Examples: + | width | height | + | 32 | 32 | + | 12 | 12 | + | 32 | 12 | + | 12 | 32 | + + + Scenario Outline: download previews of different size larger than the maximum size set + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And the administrator has updated system config key "preview_max_x" with value "32" + And the administrator has updated system config key "preview_max_y" with value "32" + When user "Alice" downloads the preview of "/parent.txt" with width "" and height "" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + Examples: + | width | height | + | 64 | 64 | + | 2048 | 2048 | + + + Scenario: preview content changes with the change in file content + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And user "Alice" has downloaded the preview of "/parent.txt" with width "32" and height "32" + When user "Alice" uploads file with content "this is a file to upload" to "/parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the preview of "/parent.txt" with width "32" and height "32" should have been changed + + @notToImplementOnOCIS + Scenario: when owner updates a shared file, previews for sharee are also updated + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And user "Alice" has shared file "/parent.txt" with user "Brian" + And user "Brian" has downloaded the preview of "/parent.txt" with width "32" and height "32" + When user "Alice" uploads file with content "this is a file to upload" to "/parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Brian" the preview of "/parent.txt" with width "32" and height "32" should have been changed + + @issue-ocis-2538 + Scenario: when owner updates a shared file, previews for sharee are also updated (to shared folder) + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + And user "Alice" has shared file "/parent.txt" with user "Brian" + And user "Brian" has accepted share "/parent.txt" offered by user "Alice" + And user "Brian" has downloaded the preview of "/Shares/parent.txt" with width "32" and height "32" + When user "Alice" uploads file with content "this is a file to upload" to "/parent.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Brian" the preview of "/Shares/parent.txt" with width "32" and height "32" should have been changed + + + Scenario: it should update the preview content if the file content is updated (content with UTF chars) + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" + And user "Alice" has uploaded file with content "सिमसिमे पानी" to "/lorem.txt" + When user "Alice" downloads the preview of "/lorem.txt" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be "32" pixels wide and "32" pixels high + And the downloaded preview content should match with "सिमसिमे-पानी.png" fixtures preview content + + + Scenario: updates to a file should change the preview for both sharees and sharers + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file with content "file to upload" to "/FOLDER/lorem.txt" + And user "Alice" has shared folder "FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Alice" has downloaded the preview of "/FOLDER/lorem.txt" with width "32" and height "32" + And user "Brian" has downloaded the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" + When user "Alice" uploads file "filesForUpload/lorem.txt" to "/FOLDER/lorem.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the preview of "/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Brian" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + When user "Brian" uploads file with content "new uploaded content" to "Shares/FOLDER/lorem.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the preview of "/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Brian" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + + + Scenario: updates to a group shared file should change the preview for both sharees and sharers + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has been added to group "grp1" + And user "Carol" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file with content "file to upload" to "/FOLDER/lorem.txt" + And user "Alice" has shared folder "/FOLDER" with group "grp1" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Carol" has accepted share "/FOLDER" offered by user "Alice" + And user "Alice" has downloaded the preview of "/FOLDER/lorem.txt" with width "32" and height "32" + And user "Brian" has downloaded the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" + And user "Carol" has downloaded the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" + When user "Alice" uploads file "filesForUpload/lorem.txt" to "/FOLDER/lorem.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the preview of "/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Brian" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Carol" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + When user "Brian" uploads file with content "new uploaded content" to "Shares/FOLDER/lorem.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as user "Alice" the preview of "/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Brian" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + And as user "Carol" the preview of "Shares/FOLDER/lorem.txt" with width "32" and height "32" should have been changed + + @notToImplementOnOCIS + Scenario: JPEG preview quality can be determined by config + Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar_low.jpg" + And the administrator has updated system config key "previewJPEGImageDisplayQuality" with value "1" + And user "Alice" downloads the preview of "/testavatar_low.jpg" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the requested JPEG image should have a quality value of "1" + Then user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar_high.jpg" + And the administrator has updated system config key "previewJPEGImageDisplayQuality" with value "100" + And user "Alice" downloads the preview of "/testavatar_high.jpg" with width "32" and height "32" using the WebDAV API + Then the HTTP status code should be "200" + And the requested JPEG image should have a quality value of "100" diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature new file mode 100644 index 00000000000..1246c1e91de --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavPreviews/previewsAutoAdustedSizing.feature @@ -0,0 +1,25 @@ +@api @preview-extension-required +Feature: sizing of previews of files downloaded through the webdav API + As a user + I want the aspect-ratio of previews to be preserved even when I ask for an unusual preview size + So that the previews always have a similar look-and-feel to the original file + + This is optional behavior of an implementation. OCIS happens like this, + but oC10 does not do this auto-fix of the aspect ratio. + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @skipOnOcV10 + Scenario Outline: download different sizes of previews of file + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width and height using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be pixels wide and pixels high + Examples: + | request_width | request_height | return_width | return_height | + | 1 | 1 | 16 | 16 | + | 32 | 32 | 32 | 32 | + | 1024 | 1024 | 640 | 480 | + | 1 | 1024 | 16 | 16 | + | 1024 | 1 | 640 | 480 | diff --git a/tests/acceptance/features/coreApiWebdavPreviews/previewsExactSizing.feature b/tests/acceptance/features/coreApiWebdavPreviews/previewsExactSizing.feature new file mode 100644 index 00000000000..f113a170b79 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavPreviews/previewsExactSizing.feature @@ -0,0 +1,25 @@ +@api @preview-extension-required +Feature: sizing of previews of files downloaded through the webdav API + As a user + I want previews to be the exact requested size even when I ask for an unusual preview size combination + So that the previews always have the exact size that I want as a user/client. + + This is optional behavior of an implementation. oC10 happens like this, + but OCIS does an auto-fix of the aspect ratio. + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + @skipOnOcis + Scenario Outline: download different sizes of previews of file + Given user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/parent.txt" + When user "Alice" downloads the preview of "/parent.txt" with width and height using the WebDAV API + Then the HTTP status code should be "200" + And the downloaded image should be pixels wide and pixels high + Examples: + | width | height | + | 1 | 1 | + | 32 | 32 | + | 1024 | 1024 | + | 1 | 1024 | + | 1024 | 1 | diff --git a/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature b/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature new file mode 100644 index 00000000000..72ce580e532 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties1/copyFile.feature @@ -0,0 +1,887 @@ +@api +Feature: copy file + As a user + I want to be able to copy files + So that I can manage my files + + Background: + Given using OCS API version "1" + And the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has uploaded file with content "ownCloud test text file 1" to "/textfile1.txt" + And user "Alice" has created folder "/FOLDER" + + @smokeTest + Scenario Outline: Copying a file + Given using DAV path + When user "Alice" copies file "/textfile0.txt" to "/FOLDER/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/FOLDER/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: Copying and overwriting a file + Given using DAV path + When user "Alice" copies file "/textfile0.txt" to "/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/textfile1.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Copying a file when 2 files exist with different case + Given using DAV path + # "/textfile1.txt" already exists in the skeleton, make another with only case differences in the file name + When user "Alice" copies file "/textfile0.txt" to "/TextFile1.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/textfile1.txt" for user "Alice" should be "ownCloud test text file 1" + And the content of file "/TextFile1.txt" for user "Alice" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: Copying a file to a folder with no permissions + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" copies file "/textfile0.txt" to "/Shares/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "403" + And user "Alice" should not be able to download file "/Shares/testshare/textfile0.txt" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: Copying a file to overwrite a file into a folder with no permissions + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "ownCloud test text file 1" to "textfile1.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | read | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + And user "Brian" has copied file "textfile1.txt" to "/testshare/overwritethis.txt" + When user "Alice" copies file "/textfile0.txt" to "/Shares/testshare/overwritethis.txt" using the WebDAV API + Then the HTTP status code should be "403" + And the content of file "/Shares/testshare/overwritethis.txt" for user "Alice" should be "ownCloud test text file 1" + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-reva-15 + Scenario Outline: Copying file to a path with extension .part should not be possible + Given using DAV path + When user "Alice" copies file "/textfile1.txt" to "/textfile1.part" using the WebDAV API + Then the HTTP status code should be "400" + And the DAV exception should be "OCA\DAV\Connector\Sabre\Exception\InvalidPath" + And the DAV message should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And the DAV reason should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And user "Alice" should see the following elements + | /textfile1.txt | + But user "Alice" should not see the following elements + | /textfile1.part | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a file over the top of an existing folder + Given using DAV path + And user "Alice" has created folder "FOLDER/sample-folder" + When user "Alice" copies file "/textfile1.txt" to "/FOLDER" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/FOLDER" for user "Alice" should be "ownCloud test text file 1" + And as "Alice" folder "/FOLDER/sample-folder" should not exist + And as "Alice" file "/textfile1.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a folder over the top of an existing file + Given using DAV path + And user "Alice" has created folder "FOLDER/sample-folder" + When user "Alice" copies folder "/FOLDER" to "/textfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/FOLDER/sample-folder" should exist + And as "Alice" folder "/textfile1.txt/sample-folder" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a folder into another folder at different level + Given using DAV path + And user "Alice" has created folder "FOLDER/second-level-folder" + And user "Alice" has created folder "FOLDER/second-level-folder/third-level-folder" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b/sample-folder-c" + When user "Alice" copies folder "Sample-Folder-A/sample-folder-b" to "FOLDER/second-level-folder/third-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/Sample-Folder-A/sample-folder-b/sample-folder-c" should exist + And as "Alice" folder "/FOLDER/second-level-folder/third-level-folder/sample-folder-c" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a file into a folder at different level + Given using DAV path + And user "Alice" has created folder "FOLDER/second-level-folder" + And user "Alice" has created folder "FOLDER/second-level-folder/third-level-folder" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "FOLDER/second-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "FOLDER/second-level-folder/third-level-folder" should not exist + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "FOLDER/second-level-folder" should exist + And the content of file "FOLDER/second-level-folder" for user "Alice" should be "sample file-c" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a file into a file at different level + Given using DAV path + And user "Alice" has uploaded file with content "file at second level" to "FOLDER/second-level-file.txt" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "FOLDER/second-level-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "FOLDER/second-level-file.txt" should exist + And as "Alice" file "FOLDER/textfile-c.txt" should not exist + And the content of file "FOLDER/second-level-file.txt" for user "Alice" should be "sample file-c" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-387 + Scenario Outline: copy a folder into a file at different level + Given using DAV path + And user "Alice" has created folder "FOLDER/second-level-folder" + And user "Alice" has created folder "FOLDER/second-level-folder/third-level-folder" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies folder "FOLDER/second-level-folder" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" folder "FOLDER/second-level-folder/third-level-folder" should exist + And as "Alice" folder "Sample-Folder-A/sample-folder-b/textfile-c.txt/third-level-folder" should exist + And as "Alice" folder "Sample-Folder-A/sample-folder-b/second-level-folder" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-1239 + Scenario Outline: copy a file over the top of an existing folder received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/BRIAN-Folder" + And user "Brian" has created folder "BRIAN-Folder/sample-folder" + And user "Brian" has shared folder "BRIAN-Folder" with user "Alice" + And user "Alice" has accepted share "/BRIAN-Folder" offered by user "Brian" + When user "Alice" copies file "/textfile1.txt" to "/Shares/BRIAN-Folder" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/Shares/BRIAN-Folder" for user "Alice" should be "ownCloud test text file 1" + And as "Alice" folder "/Shares/BRIAN-Folder/sample-folder" should not exist + And as "Alice" file "/textfile1.txt" should exist + And user "Alice" should not have any received shares + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a folder over the top of an existing file received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has uploaded file with content "file to share" to "/sharedfile1.txt" + And user "Brian" has shared file "/sharedfile1.txt" with user "Alice" + And user "Alice" has accepted share "/sharedfile1.txt" offered by user "Brian" + And user "Alice" has created folder "FOLDER/sample-folder" + When user "Alice" copies folder "/FOLDER" to "/Shares/sharedfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/FOLDER/sample-folder" should exist + And as "Alice" folder "/Shares/sharedfile1.txt/sample-folder" should exist + And user "Alice" should not have any received shares + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a folder into another folder at different level which is received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder/third-level-folder" + And user "Brian" has shared folder "BRIAN-FOLDER" with user "Alice" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b/sample-folder-c" + When user "Alice" copies folder "Sample-Folder-A/sample-folder-b" to "Shares/BRIAN-FOLDER/second-level-folder/third-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/Sample-Folder-A/sample-folder-b/sample-folder-c" should exist + And as "Alice" folder "/Shares/BRIAN-FOLDER/second-level-folder/third-level-folder/sample-folder-c" should exist + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a file into a folder at different level received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder/third-level-folder" + And user "Brian" has shared folder "BRIAN-FOLDER" with user "Alice" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "Shares/BRIAN-FOLDER/second-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-folder" should not exist + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/second-level-folder" should exist + And the content of file "Shares/BRIAN-FOLDER/second-level-folder" for user "Alice" should be "sample file-c" + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a file into a file at different level received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has uploaded file with content "file at second level" to "BRIAN-FOLDER/second-level-file.txt" + And user "Brian" has shared folder "BRIAN-FOLDER" with user "Alice" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "Shares/BRIAN-FOLDER/second-level-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/second-level-file.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/textfile-c.txt" should not exist + And the content of file "Shares/BRIAN-FOLDER/second-level-file.txt" for user "Alice" should be "sample file-c" + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a folder into a file at different level received as a user share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has uploaded file with content "file at third level" to "BRIAN-FOLDER/second-level-folder/third-level-file.txt" + And user "Brian" has shared folder "BRIAN-FOLDER" with user "Alice" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "FOLDER/second-level-folder" + And user "Alice" has created folder "FOLDER/second-level-folder/third-level-folder" + When user "Alice" copies folder "FOLDER/second-level-folder" to "/Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt" should exist + And as "Alice" folder "FOLDER/second-level-folder/third-level-folder" should exist + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt/third-level-folder" should exist + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/second-level-folder" should not exist + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a file over the top of an existing folder received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has created folder "/BRIAN-Folder" + And user "Brian" has created folder "BRIAN-Folder/sample-folder" + And user "Brian" has shared folder "BRIAN-Folder" with group "grp1" with permissions "15" + And user "Alice" has accepted share "/BRIAN-Folder" offered by user "Brian" + When user "Alice" copies file "/textfile1.txt" to "/Shares/BRIAN-Folder" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/Shares/BRIAN-Folder" for user "Alice" should be "ownCloud test text file 1" + And as "Alice" folder "/Shares/BRIAN-Folder/sample-folder" should not exist + And as "Alice" file "/textfile1.txt" should exist + And user "Alice" should not have any received shares + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-1239 + Scenario Outline: copy a folder over the top of an existing file received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has uploaded file with content "file to share" to "/sharedfile1.txt" + And user "Brian" has shared file "/sharedfile1.txt" with group "grp1" + And user "Alice" has accepted share "/sharedfile1.txt" offered by user "Brian" + And user "Alice" has created folder "FOLDER/sample-folder" + When user "Alice" copies folder "/FOLDER" to "/Shares/sharedfile1.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/FOLDER/sample-folder" should exist + And as "Alice" folder "/Shares/sharedfile1.txt/sample-folder" should exist + And user "Alice" should not have any received shares + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-1239 + Scenario Outline: copy a folder into another folder at different level which is received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder/third-level-folder" + And user "Brian" has shared folder "BRIAN-FOLDER" with group "grp1" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b/sample-folder-c" + When user "Alice" copies folder "Sample-Folder-A/sample-folder-b" to "Shares/BRIAN-FOLDER/second-level-folder/third-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "/Sample-Folder-A/sample-folder-b/sample-folder-c" should exist + And as "Alice" folder "/Shares/BRIAN-FOLDER/second-level-folder/third-level-folder/sample-folder-c" should exist + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a file into a folder at different level received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder/third-level-folder" + And user "Brian" has shared folder "BRIAN-FOLDER" with group "grp1" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "Shares/BRIAN-FOLDER/second-level-folder" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-folder" should not exist + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/second-level-folder" should exist + And the content of file "Shares/BRIAN-FOLDER/second-level-folder" for user "Alice" should be "sample file-c" + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a file into a file at different level received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has uploaded file with content "file at second level" to "BRIAN-FOLDER/second-level-file.txt" + And user "Brian" has shared folder "BRIAN-FOLDER" with group "grp1" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "Sample-Folder-A" + And user "Alice" has created folder "Sample-Folder-A/sample-folder-b" + And user "Alice" has uploaded file with content "sample file-c" to "Sample-Folder-A/sample-folder-b/textfile-c.txt" + When user "Alice" copies file "Sample-Folder-A/sample-folder-b/textfile-c.txt" to "Shares/BRIAN-FOLDER/second-level-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "Sample-Folder-A/sample-folder-b/textfile-c.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/second-level-file.txt" should exist + And as "Alice" file "Shares/BRIAN-FOLDER/textfile-c.txt" should not exist + And the content of file "Shares/BRIAN-FOLDER/second-level-file.txt" for user "Alice" should be "sample file-c" + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-1239 + Scenario Outline: copy a folder into a file at different level received as a group share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp1" + And user "Brian" has created folder "BRIAN-FOLDER" + And user "Brian" has created folder "BRIAN-FOLDER/second-level-folder" + And user "Brian" has uploaded file with content "file at third level" to "BRIAN-FOLDER/second-level-folder/third-level-file.txt" + And user "Brian" has shared folder "BRIAN-FOLDER" with group "grp1" + And user "Alice" has accepted share "/BRIAN-FOLDER" offered by user "Brian" + And user "Alice" has created folder "FOLDER/second-level-folder" + And user "Alice" has created folder "FOLDER/second-level-folder/third-level-folder" + When user "Alice" copies folder "FOLDER/second-level-folder" to "Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt" using the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt" should exist + And as "Alice" folder "FOLDER/second-level-folder/third-level-folder" should exist + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/third-level-file.txt/third-level-folder" should exist + And as "Alice" folder "Shares/BRIAN-FOLDER/second-level-folder/second-level-folder" should not exist + And the response when user "Alice" gets the info of the last share should include + | file_target | /Shares/BRIAN-FOLDER | + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: copy a file of size zero byte + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" + And user "Alice" has created folder "/testZeroByte" + When user "Alice" copies file "/zerobyte.txt" to "/testZeroByte/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/testZeroByte/zerobyte.txt" should exist + And as "Alice" file "/zerobyte.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Copy file into a nonexistent folder + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToCopy.txt" + When user "Alice" copies file "/fileToCopy.txt" to "/not-existing-folder/fileToCopy.txt" using the WebDAV API + Then the HTTP status code should be "409" + And as "Alice" file "/fileToCopy.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Copy a nonexistent file into a folder + Given using DAV path + When user "Alice" copies file "/doesNotExist.txt" to "/FOLDER/doesNotExist.txt" using the WebDAV API + Then the HTTP status code should be "404" + And as "Alice" file "/FOLDER/doesNotExist.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Copy a folder into a nonexistent one + Given using DAV path + And user "Alice" has created folder "/testshare" + When user "Alice" copies folder "/testshare" to "/not-existing/testshare" using the WebDAV API + Then the HTTP status code should be "409" + And user "Alice" should see the following elements + | /testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required + Scenario Outline: Copying a file into a shared folder as the sharee + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Alice" copies file "/textfile0.txt" to "/Shares/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/Shares/testshare/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "/testshare/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: Copying a file into a shared folder as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Brian" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Brian" copies file "/textfile0.txt" to "/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/Shares/testshare/textfile0.txt" for user "Alice" should be "ownCloud test text file 0" + And the content of file "/testshare/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: Copying a file out of a shared folder as the sharee + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + And user "Alice" has uploaded file with content "ownCloud test text file inside share" to "/Shares/testshare/fileInsideShare.txt" + When user "Alice" copies file "/Shares/testshare/fileInsideShare.txt" to "/fileInsideShare.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/fileInsideShare.txt" should exist + And the content of file "/fileInsideShare.txt" for user "Alice" should be "ownCloud test text file inside share" + And the content of file "/testshare/fileInsideShare.txt" for user "Brian" should be "ownCloud test text file inside share" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: Copying a file out of a shared folder as the sharer + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare" + And user "Brian" has uploaded file with content "ownCloud test text file inside share" to "/testshare/fileInsideShare.txt" + And user "Brian" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare" offered by user "Brian" + When user "Brian" copies file "testshare/fileInsideShare.txt" to "/fileInsideShare.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/fileInsideShare.txt" should exist + And the content of file "/Shares/testshare/fileInsideShare.txt" for user "Alice" should be "ownCloud test text file inside share" + And the content of file "/fileInsideShare.txt" for user "Brian" should be "ownCloud test text file inside share" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Copying a hidden file + Given using DAV path + And user "Alice" has uploaded the following files with content "hidden file" + | path | + | .hidden_file101 | + | /FOLDER/.hidden_file102 | + When user "Alice" copies file ".hidden_file101" to "/FOLDER/.hidden_file101" using the WebDAV API + And user "Alice" copies file "/FOLDER/.hidden_file102" to ".hidden_file102" using the WebDAV API + And as "Alice" the following files should exist + | path | + | .hidden_file102 | + | /FOLDER/.hidden_file101 | + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file102 | + | /FOLDER/.hidden_file101 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required + Scenario Outline: Copying a file between shares received from different users + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare0" + And user "Brian" has uploaded file with content "content inside testshare0" to "/testshare0/testshare0.txt" + And user "Carol" has created folder "/testshare1" + And user "Brian" has created a share with settings + | path | testshare0 | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Carol" has created a share with settings + | path | testshare1 | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare0" offered by user "Brian" + And user "Alice" has accepted share "/testshare1" offered by user "Carol" + When user "Alice" copies file "/Shares/testshare0/testshare0.txt" to "/Shares/testshare1/testshare0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "testshare1/testshare0.txt" should exist + And as "Alice" file "Shares/testshare1/testshare0.txt" should exist + And the content of file "/testshare1/testshare0.txt" for user "Carol" should be "content inside testshare0" + And the content of file "/Shares/testshare1/testshare0.txt" for user "Alice" should be "content inside testshare0" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: Copying a folder between shares received from different users + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has created folder "/testshare0" + And user "Brian" has created folder "/testshare0/folder_to_copy/" + And user "Brian" has uploaded file with content "content inside testshare0" to "/testshare0/folder_to_copy/testshare0.txt" + And user "Carol" has created folder "/testshare1" + And user "Brian" has created a share with settings + | path | testshare0 | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Carol" has created a share with settings + | path | testshare1 | + | shareType | user | + | permissions | change | + | shareWith | Alice | + And user "Alice" has accepted share "/testshare0" offered by user "Brian" + And user "Alice" has accepted share "/testshare1" offered by user "Carol" + When user "Alice" copies file "/Shares/testshare0/folder_to_copy/" to "/Shares/testshare1/folder_to_copy/" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "testshare1/folder_to_copy/testshare0.txt" should exist + And as "Alice" file "/Shares/testshare1/folder_to_copy/testshare0.txt" should exist + And the content of file "testshare1/folder_to_copy/testshare0.txt" for user "Carol" should be "content inside testshare0" + And the content of file "/Shares/testshare1/folder_to_copy/testshare0.txt" for user "Alice" should be "content inside testshare0" + Examples: + | dav_version | + | old | + | new | + + @files_sharing-app-required + Scenario Outline: Copying a file to a folder that is shared with multiple users + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/testshare" + And user "Alice" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Brian | + And user "Brian" has accepted share "/testshare" offered by user "Alice" + And user "Alice" has created a share with settings + | path | testshare | + | shareType | user | + | permissions | change | + | shareWith | Carol | + And user "Carol" has accepted share "/testshare" offered by user "Alice" + When user "Alice" copies file "/textfile0.txt" to "/testshare/textfile0.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" file "/Shares/testshare/textfile0.txt" should exist + And as "Carol" file "/Shares/testshare/textfile0.txt" should exist + And the content of file "/Shares/testshare/textfile0.txt" for user "Brian" should be "ownCloud test text file 0" + And the content of file "/Shares/testshare/textfile0.txt" for user "Carol" should be "ownCloud test text file 0" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Copy a folder into another one + Given using DAV path + And user "Alice" has created folder "/testshare" + And user "Alice" has created folder "/an-other-folder" + When user "Alice" copies folder "/testshare" to "/an-other-folder/testshare" using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" should see the following elements + | /testshare/ | + And user "Alice" should see the following elements + | /an-other-folder/testshare/ | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-3023 + Scenario Outline: Copying a folder into a sub-folder of itself + Given using DAV path + And user "Alice" has created folder "/PARENT" + And user "Alice" has created folder "/PARENT/CHILD" + And user "Alice" has uploaded file with content "parent text" to "/PARENT/parent.txt" + And user "Alice" has uploaded file with content "child text" to "/PARENT/CHILD/child.txt" + When user "Alice" copies folder "/PARENT" to "/PARENT/CHILD/PARENT" using the WebDAV API + Then the HTTP status code should be "409" + And the content of file "/PARENT/parent.txt" for user "Alice" should be "parent text" + And the content of file "/PARENT/CHILD/child.txt" for user "Alice" should be "child text" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Copying a folder with a file into another folder + Given using DAV path + And user "Alice" has created folder "/FOLDER1" + And user "Alice" has created folder "/FOLDER2" + And user "Alice" has uploaded file with content "Folder 1 text" to "/FOLDER1/textfile.txt" + When user "Alice" copies folder "/FOLDER1" to "/FOLDER2/FOLDER1" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "/FOLDER2/FOLDER1" should exist + And as "Alice" file "/FOLDER2/FOLDER1/textfile.txt" should exist + And as "Alice" folder "/FOLDER1" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature new file mode 100644 index 00000000000..16d8504f3cb --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolder.feature @@ -0,0 +1,180 @@ +@api +Feature: create files and folder + As a user + I want to be able to create files and folders + So that I can organise the files in my file system + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @issue-ocis-reva-269 + Scenario Outline: create a folder + Given using DAV path + When user "Alice" creates folder "" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" folder "" should exist + Examples: + | dav_version | folder_name | + | old | /upload | + | old | /strängé folder | + | old | /C++ folder.cpp | + | old | /नेपाली | + | old | /folder #2 | + | old | /folder ?2 | + | old | /😀 🤖 | + | old | /new&folder | + | new | /upload | + | new | /strängé folder | + | new | /C++ folder.cpp | + | new | /नेपाली | + | new | /folder #2 | + | new | /folder ?2 | + | new | /😀 🤖 | + | new | /new&folder | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | + | spaces | /upload | + | spaces | /strängé folder | + | spaces | /C++ folder.cpp | + | spaces | /नेपाली | + | spaces | /folder #2 | + | spaces | /folder ?2 | + | spaces | /😀 🤖 | + | spaces | /new&folder | + + @smokeTest + Scenario Outline: Creating a folder + Given using DAV path + And user "Alice" has created folder "/test_folder" + When user "Alice" gets the following properties of folder "/test_folder" using the WebDAV API + | propertyName | + | d:resourcetype | + Then the HTTP status code should be "201" + And the single response should contain a property "d:resourcetype" with a child property "d:collection" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Creating a folder with special chars + Given using DAV path + And user "Alice" has created folder "/test_folder:5" + When user "Alice" gets the following properties of folder "/test_folder:5" using the WebDAV API + | propertyName | + | d:resourcetype | + Then the HTTP status code should be "201" + And the single response should contain a property "d:resourcetype" with a child property "d:collection" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-15 + Scenario Outline: Creating a directory which contains .part should not be possible + Given using DAV path + When user "Alice" creates folder "/folder.with.ext.part" using the WebDAV API + Then the HTTP status code should be "400" + And the DAV exception should be "OCA\DAV\Connector\Sabre\Exception\InvalidPath" + And the DAV message should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And the DAV reason should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And user "Alice" should not see the following elements + | /folder.with.ext.part | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-168 + Scenario Outline: try to create a folder that already exists + Given using DAV path + And user "Alice" has created folder "my-data" + When user "Alice" creates folder "my-data" using the WebDAV API + Then the HTTP status code should be "405" + And as "Alice" folder "my-data" should exist + And the DAV exception should be "Sabre\DAV\Exception\MethodNotAllowed" + And the DAV message should be "The resource you tried to create already exists" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-168 + Scenario Outline: try to create a folder with a name of an existing file + Given using DAV path + And user "Alice" has uploaded file with content "uploaded data" to "/my-data.txt" + When user "Alice" creates folder "my-data.txt" using the WebDAV API + Then the HTTP status code should be "405" + And the DAV exception should be "Sabre\DAV\Exception\MethodNotAllowed" + And the DAV message should be "The resource you tried to create already exists" + And the content of file "/my-data.txt" for user "Alice" should be "uploaded data" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Create a file + Given using DAV path + When user "Alice" uploads file with content "some text" to "" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "" should exist + And the content of file "" for user "Alice" should be "some text" + Examples: + | dav_version | file_name | + | old | /upload.txt | + | old | /strängéfile.txt | + | old | /C++ file.cpp | + | old | /नेपाली | + | old | /file #2.txt | + | old | /file ?2.pdf | + | old | /😀 🤖.txt | + | old | /new&file.txt | + | new | /upload.txt | + | new | /strängéfile.txt | + | new | /C++ file.cpp | + | new | /नेपाली | + | new | /file #2.txt | + | new | /file ?2.pdf | + | new | /😀 🤖.txt | + | new | /new&file.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | /upload.txt | + | spaces | /strängéfile.txt | + | spaces | /C++ file.cpp | + | spaces | /नेपाली | + | spaces | /file #2.txt | + | spaces | /file ?2.pdf | + | spaces | /😀 🤖.txt | + | spaces | /new&file.txt | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavProperties1/createFileFolderWhenSharesExist.feature b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolderWhenSharesExist.feature new file mode 100644 index 00000000000..39460125c73 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties1/createFileFolderWhenSharesExist.feature @@ -0,0 +1,72 @@ +@api +Feature: create file or folder named similar to Shares folder + As a user + I want to be able to create files and folders when the Shares folder exists + So that I can organise the files in my file system + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read,update" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + + + Scenario Outline: create a folder with a name similar to Shares + Given using DAV path + When user "Brian" creates folder "" using the WebDAV API + Then the HTTP status code should be "201" + And as "Brian" folder "" should exist + And as "Brian" folder "/Shares" should exist + Examples: + | dav_version | folder_name | + | old | /Share | + | old | /shares | + | old | /Shares1 | + | new | /Share | + | new | /shares | + | new | /Shares1 | + + + Scenario Outline: create a file with a name similar to Shares + Given using DAV path + When user "Brian" uploads file with content "some text" to "" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "" for user "Brian" should be "some text" + And as "Brian" folder "/Shares" should exist + Examples: + | dav_version | file_name | + | old | /Share | + | old | /shares | + | old | /Shares1 | + | new | /Share | + | new | /shares | + | new | /Shares1 | + + + Scenario Outline: try to create a folder named Shares + Given using DAV path + When user "Brian" creates folder "/Shares" using the WebDAV API + Then the HTTP status code should be "405" + And as "Brian" folder "/Shares" should exist + And as "Brian" folder "/Shares/FOLDER" should exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: try to create a file named Shares + Given using DAV path + When user "Brian" uploads file with content "some text" to "/Shares" using the WebDAV API + Then the HTTP status code should be "409" + And as "Brian" folder "/Shares" should exist + And as "Brian" folder "/Shares/FOLDER" should exist + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature b/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature new file mode 100644 index 00000000000..652a4e9748f --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties1/getQuota.feature @@ -0,0 +1,110 @@ +@api @issue-ocis-reva-101 @skipOnGraph +Feature: get quota + As a user + I want to be able to find out my available storage quota + So that I can manage the use of my allocated storage + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and small skeleton files + + + Scenario Outline: Retrieving folder quota when no quota is set + Given using DAV path + When the administrator gives unlimited quota to user "Alice" using the provisioning API + Then the HTTP status code should be "200" + And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "-3" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + Scenario Outline: Retrieving folder quota when quota is set + Given using DAV path + When the administrator sets the quota of user "Alice" to "10 MB" using the provisioning API + Then the HTTP status code should be "200" + And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "10485406" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required + Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient + Given using DAV path + And user "Brian" has been created with default attributes and small skeleton files + And user "Alice" has been given unlimited quota + And the quota of user "Brian" has been set to "10 MB" + And user "Brian" has created folder "/testquota" + And user "Brian" has created a share with settings + | path | testquota | + | shareType | user | + | permissions | all | + | shareWith | Alice | + When user "Alice" gets the following properties of folder "/testquota" using the WebDAV API + | propertyName | + | d:quota-available-bytes | + Then the HTTP status code should be "200" + And the single response should contain a property "d:quota-available-bytes" with value "10485406" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded + Given using DAV path + And the quota of user "Alice" has been set to "1 KB" + And user "Alice" has uploaded file "/prueba.txt" of size 93 bytes + When user "Alice" gets the following properties of folder "/" using the WebDAV API + | propertyName | + | d:quota-available-bytes | + Then the HTTP status code should be "201" + And the single response should contain a property "d:quota-available-bytes" with value "577" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required + Scenario Outline: Retrieving folder quota when quota is set and a file was received + Given using DAV path + And user "Brian" has been created with default attributes and small skeleton files + And the quota of user "Brian" has been set to "1 KB" + And user "Alice" has uploaded file "/Alice.txt" of size 93 bytes + And user "Alice" has shared file "Alice.txt" with user "Brian" + When user "Brian" gets the following properties of folder "/" using the WebDAV API + | propertyName | + | d:quota-available-bytes | + Then the HTTP status code should be "200" + And the single response should contain a property "d:quota-available-bytes" with value "670" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature b/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature new file mode 100644 index 00000000000..2f735a6d2a8 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties1/setFileProperties.feature @@ -0,0 +1,105 @@ +@api +Feature: set file properties + As a user + I want to be able to set meta-information about files + So that I can reccord file meta-information (detailed requirement TBD) + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest @issue-ocis-reva-276 + Scenario Outline: Setting custom DAV property and reading it + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt" + And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "veryCustomPropValue" + When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" + Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "veryCustomPropValue" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-217 + Scenario Outline: Setting custom complex DAV property and reading it + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt" + And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "" + When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" + Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and complex value "" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-276 + Scenario Outline: Setting custom DAV property and reading it after the file is renamed + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustompropwithmove.txt" + And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropwithmove.txt" to "valueForMovetest" + And user "Alice" has moved file "/testcustompropwithmove.txt" to "/catchmeifyoucan.txt" + When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/catchmeifyoucan.txt" + Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "valueForMovetest" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @issue-ocis-reva-217 + Scenario Outline: Setting custom DAV property on a shared file as an owner and reading as a recipient + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustompropshared.txt" + And user "Alice" has created a share with settings + | path | testcustompropshared.txt | + | shareType | user | + | permissions | all | + | shareWith | Brian | + And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropshared.txt" to "valueForSharetest" + When user "Brian" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropshared.txt" + Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "valueForSharetest" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-276 + Scenario Outline: Setting custom DAV property using one endpoint and reading it with other endpoint + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testnewold.txt" + And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testnewold.txt" to "lucky" + And using DAV path + When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testnewold.txt" + Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "lucky" + Examples: + | action_dav_version | other_dav_version | + | old | new | + | new | old | + + @skipOnOcV10 @personalSpace + Examples: + | action_dav_version | other_dav_version | + | spaces | new | + | spaces | old | + | new | spaces | + | old | spaces | diff --git a/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature b/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature new file mode 100644 index 00000000000..c3fddf28117 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavProperties2/getFileProperties.feature @@ -0,0 +1,657 @@ +@api +Feature: get file properties + As a user + I want to be able to get meta-information about files + So that I can know file meta-information (detailed requirement TBD) + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: Do a PROPFIND of various file names + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "" + When user "Alice" gets the properties of file "" using the WebDAV API + Then the HTTP status code should be "201" + And the properties response should contain an etag + Examples: + | dav_version | file_name | + | old | /upload.txt | + | old | /strängé file.txt | + | old | /नेपाली.txt | + | old | s,a,m,p,l,e.txt | + | new | /upload.txt | + | new | /strängé file.txt | + | new | /नेपाली.txt | + | new | s,a,m,p,l,e.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | /upload.txt | + | spaces | /strängé file.txt | + | spaces | /नेपाली.txt | + | spaces | s,a,m,p,l,e.txt | + + @issue-ocis-reva-214 + Scenario Outline: Do a PROPFIND of various file names + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "" + When user "Alice" gets the properties of file "" using the WebDAV API + Then the HTTP status code should be "201" + And the properties response should contain an etag + And there should be an entry with href containing "" in the response to user "Alice" + Examples: + | dav_version | file_name | expected_href | + | old | /C++ file.cpp | remote.php/webdav/C++ file.cpp | + | old | /file #2.txt | remote.php/webdav/file #2.txt | + | old | /file ?2.txt | remote.php/webdav/file ?2.txt | + | old | /file &2.txt | remote.php/webdav/file &2.txt | + | new | /C++ file.cpp | remote.php/dav/files/%username%/C++ file.cpp | + | new | /file #2.txt | remote.php/dav/files/%username%/file #2.txt | + | new | /file ?2.txt | remote.php/dav/files/%username%/file ?2.txt | + | new | /file &2.txt | remote.php/dav/files/%username%/file &2.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | expected_href | + | spaces | /C++ file.cpp | dav/spaces/%spaceid%/C++ file.cpp | + | spaces | /file #2.txt | dav/spaces/%spaceid%/file #2.txt | + | spaces | /file ?2.txt | dav/spaces/%spaceid%/file ?2.txt | + | spaces | /file &2.txt | dav/spaces/%spaceid%/file &2.txt | + + @issue-ocis-reva-214 + Scenario Outline: Do a PROPFIND of various folder names + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content" to "/file1.txt" + And user "Alice" has uploaded file with content "uploaded content" to "/file2.txt" + When user "Alice" gets the properties of folder "" with depth 1 using the WebDAV API + Then the HTTP status code should be "201" + And there should be an entry with href containing "/" in the response to user "Alice" + And there should be an entry with href containing "/file1.txt" in the response to user "Alice" + And there should be an entry with href containing "/file2.txt" in the response to user "Alice" + Examples: + | dav_version | folder_name | expected_href | + | old | /upload | remote.php/webdav/upload | + | old | /strängé folder | remote.php/webdav/strängé folder | + | old | /C++ folder | remote.php/webdav/C++ folder | + | old | /नेपाली | remote.php/webdav/नेपाली | + | old | /folder #2.txt | remote.php/webdav/folder #2.txt | + | old | /folder ?2.txt | remote.php/webdav/folder ?2.txt | + | old | /folder &2.txt | remote.php/webdav/folder &2.txt | + | new | /upload | remote.php/dav/files/%username%/upload | + | new | /strängé folder | remote.php/dav/files/%username%/strängé folder | + | new | /C++ folder | remote.php/dav/files/%username%/C++ folder | + | new | /नेपाली | remote.php/dav/files/%username%/नेपाली | + | new | /folder #2.txt | remote.php/dav/files/%username%/folder #2.txt | + | new | /folder ?2.txt | remote.php/dav/files/%username%/folder ?2.txt | + | new | /folder &2.txt | remote.php/dav/files/%username%/folder &2.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | expected_href | + | spaces | /upload | dav/spaces/%spaceid%/upload | + | spaces | /strängé folder | dav/spaces/%spaceid%/strängé folder | + | spaces | /C++ folder | dav/spaces/%spaceid%/C++ folder | + | spaces | /नेपाली | dav/spaces/%spaceid%/नेपाली | + | spaces | /folder #2.txt | dav/spaces/%spaceid%/folder #2.txt | + | spaces | /folder ?2.txt | dav/spaces/%spaceid%/folder ?2.txt | + | spaces | /folder &2.txt | dav/spaces/%spaceid%/folder &2.txt | + + + Scenario Outline: Do a PROPFIND of various files inside various folders + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content" to "/" + When user "Alice" gets the properties of file "/" using the WebDAV API + Then the HTTP status code should be "201" + And the properties response should contain an etag + Examples: + | dav_version | folder_name | file_name | + | old | /upload | abc.txt | + | old | /strängé folder | strängé file.txt | + | old | /C++ folder | C++ file.cpp | + | old | /नेपाली | नेपाली | + | old | /folder #2.txt | file #2.txt | + | new | /upload | abc.txt | + | new | /strängé folder (duplicate #2 &) | strängé file (duplicate #2 &) | + | new | /C++ folder | C++ file.cpp | + | new | /नेपाली | नेपाली | + | new | /folder #2.txt | file #2.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /upload | abc.txt | + | spaces | /strängé folder | strängé file.txt | + | spaces | /C++ folder | C++ file.cpp | + | spaces | /नेपाली | नेपाली | + | spaces | /folder #2.txt | file #2.txt | + + @issue-ocis-reva-265 + #after fixing all issues delete this Scenario and merge with the one above + Scenario Outline: Do a PROPFIND of various files inside various folders + Given using DAV path + And user "Alice" has created folder "" + And user "Alice" has uploaded file with content "uploaded content" to "/" + When user "Alice" gets the properties of file "/" using the WebDAV API + Then the HTTP status code should be "201" + And the properties response should contain an etag + Examples: + | dav_version | folder_name | file_name | + | old | /folder ?2.txt | file ?2.txt | + | new | /folder ?2.txt | file ?2.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /folder ?2.txt | file ?2.txt | + + + Scenario Outline: A file that is not shared does not have a share-types property + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:share-types | + Then the HTTP status code should be "201" + And the response should contain an empty property "oc:share-types" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: A file that is shared to a user has a share-types property + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/test" + And user "Alice" has created a share with settings + | path | test | + | shareType | user | + | permissions | all | + | shareWith | Brian | + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:share-types | + Then the HTTP status code should be "200" + And the response should contain a share-types property with + | 0 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: A file that is shared to a group has a share-types property + Given using DAV path + And group "grp1" has been created + And user "Alice" has created folder "/test" + And user "Alice" has created a share with settings + | path | test | + | shareType | group | + | permissions | all | + | shareWith | grp1 | + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:share-types | + Then the HTTP status code should be "200" + And the response should contain a share-types property with + | 1 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: A file that is shared by link has a share-types property + Given using DAV path + And user "Alice" has created folder "/test" + And user "Alice" has created a public link share with settings + | path | test | + | permissions | all | + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:share-types | + Then the HTTP status code should be "200" + And the response should contain a share-types property with + | 3 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @skipOnLDAP @user_ldap-issue-268 @public_link_share-feature-required @files_sharing-app-required @issue-ocis-reva-11 + Scenario Outline: A file that is shared by user,group and link has a share-types property + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And group "grp1" has been created + And user "Alice" has created folder "/test" + And user "Alice" has created a share with settings + | path | test | + | shareType | user | + | permissions | all | + | shareWith | Brian | + And user "Alice" has created a share with settings + | path | test | + | shareType | group | + | permissions | all | + | shareWith | grp1 | + And user "Alice" has created a public link share with settings + | path | test | + | permissions | all | + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:share-types | + Then the HTTP status code should be "200" + And the response should contain a share-types property with + | 0 | + | 1 | + | 3 | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @notToImplementOnOCIS + Scenario Outline: Doing a PROPFIND with a web login should work with CSRF token on the new backend + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/somefile.txt" + And user "Alice" has logged in to a web-style session + When the client sends a "PROPFIND" to "/remote.php/dav/files/%username%/somefile.txt" of user "Alice" with requesttoken + Then the HTTP status code should be "207" + Examples: + | dav_version | + | old | + | new | + + @smokeTest @issue-ocis-reva-216 + Scenario Outline: Retrieving private link + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/somefile.txt" + When user "Alice" gets the following properties of file "/somefile.txt" using the WebDAV API + | propertyName | + | oc:privatelink | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:privatelink" with value like "%(/(index.php/)?f/[0-9]*)%" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Do a PROPFIND to a nonexistent URL + When user "Alice" requests "" with "PROPFIND" using basic auth + Then the HTTP status code should be "404" + And the value of the item "/d:error/s:message" in the response about user "Alice" should be "" or "" + And the value of the item "/d:error/s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound" + + @skipOnOcV10 + Examples: + | url | message1 | message2 | + | /remote.php/dav/files/does-not-exist | Resource not found | Resource not found | + | /remote.php/dav/does-not-exist | File not found in root | | + + @skipOnOcis + Examples: + | url | message1 | message2 | + | /remote.php/dav/files/does-not-exist | Principal with name does-not-exist not found | | + | /remote.php/dav/does-not-exist | File not found: does-not-exist in 'root' | | + + @skipOnOcV10 @personalSpace + Examples: + | url | message1 | message2 | + | /remote.php/dav/spaces/%spaceid%/does-not-exist | Resource not found | | + | /remote.php/dav/spaces/%spaceid%/file1.txt | Resource not found | | + + @issue-ocis-reva-217 + Scenario Outline: add, receive multiple custom meta properties to a file + Given using DAV path + And user "Alice" has created folder "/TestFolder" + And user "Alice" has uploaded file with content "test data one" to "/TestFolder/test1.txt" + And user "Alice" has set the following properties of file "/TestFolder/test1.txt" using the WebDav API + | propertyName | propertyValue | + | testprop1 | AAAAA | + | testprop2 | BBBBB | + When user "Alice" gets the following properties of file "/TestFolder/test1.txt" using the WebDAV API + | propertyName | + | oc:testprop1 | + | oc:testprop2 | + Then the HTTP status code should be success + And as user "Alice" the last response should have the following properties + | resource | propertyName | propertyValue | + | /TestFolder/test1.txt | testprop1 | AAAAA | + | /TestFolder/test1.txt | testprop2 | BBBBB | + | /TestFolder/test1.txt | status | HTTP/1.1 200 OK | + Examples: + | dav_version | + | new | + + @skipOnOcV10 + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-36920 @issue-ocis-reva-217 + Scenario Outline: add multiple properties to files inside a folder and do a propfind of the parent folder + Given using DAV path + And user "Alice" has created folder "/TestFolder" + And user "Alice" has uploaded file with content "test data one" to "/TestFolder/test1.txt" + And user "Alice" has uploaded file with content "test data two" to "/TestFolder/test2.txt" + And user "Alice" has set the following properties of file "/TestFolder/test1.txt" using the WebDav API + | propertyName | propertyValue | + | testprop1 | AAAAA | + | testprop2 | BBBBB | + And user "Alice" has set the following properties of file "/TestFolder/test2.txt" using the WebDav API + | propertyName | propertyValue | + | testprop1 | CCCCC | + | testprop2 | DDDDD | + When user "Alice" gets the following properties of folder "/TestFolder" using the WebDAV API + | propertyName | + | oc:testprop1 | + | oc:testprop2 | + Then the HTTP status code should be success + And as user "Alice" the last response should have the following properties + | resource | propertyName | propertyValue | + | /TestFolder/test1.txt | testprop1 | AAAAA | + | /TestFolder/test1.txt | testprop2 | BBBBB | + | /TestFolder/test2.txt | testprop1 | CCCCC | + | /TestFolder/test2.txt | testprop2 | DDDDD | + | /TestFolder/ | status | HTTP/1.1 404 Not Found | + Examples: + | dav_version | + | new | + + @skipOnOcV10 + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the last modified date of a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | d:getlastmodified | + Then the HTTP status code should be "201" + And the single response should contain a property "d:getlastmodified" with value like "/^[MTWFS][uedhfriatno]{2},\s(\d){2}\s[JFMAJSOND][anebrpyulgctov]{2}\s\d{4}\s\d{2}:\d{2}:\d{2} GMT$/" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the content type of a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | d:getcontenttype | + Then the HTTP status code should be "201" + And the single response should contain a property "d:getcontenttype" with value "" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the content type of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | d:getcontenttype | + Then the HTTP status code should be "201" + And the single response should contain a property "d:getcontenttype" with value "text/plain.*" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the etag of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | d:getetag | + Then the HTTP status code should be "201" + And the single response should contain a property "d:getetag" with value like '%\"[a-z0-9:]{1,32}\"%' + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the resource type of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | d:resourcetype | + Then the HTTP status code should be "201" + And the single response should contain a property "d:resourcetype" with value "" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the size of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | oc:size | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:size" with value "16" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the size of a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:size | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:size" with value "0" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the file id of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | oc:fileid | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:fileid" with value like '/[a-zA-Z0-9]+/' + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the file id of a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:fileid | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:fileid" with value like '/[a-zA-Z0-9]+/' + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the owner display name of a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of file "file.txt" using the WebDAV API + | propertyName | + | oc:owner-display-name | + Then the HTTP status code should be "201" + And the single response about the file owned by "Alice" should contain a property "oc:owner-display-name" with value "%displayname%" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the owner display name of a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:owner-display-name | + Then the HTTP status code should be "201" + And the single response about the file owned by "Alice" should contain a property "oc:owner-display-name" with value "%displayname%" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the permissions on a file using webdav api + Given using DAV path + And user "Alice" has uploaded file with content "uploaded content" to "file.txt" + When user "Alice" gets the following properties of folder "file.txt" using the WebDAV API + | propertyName | + | oc:permissions | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:permissions" with value like '/RM{0,1}DNVW/' + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Propfind the permissions on a folder using webdav api + Given using DAV path + And user "Alice" has created folder "/test" + When user "Alice" gets the following properties of folder "/test" using the WebDAV API + | propertyName | + | oc:permissions | + Then the HTTP status code should be "201" + And the single response should contain a property "oc:permissions" with value like '/RM{0,1}DNVCK/' + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature new file mode 100644 index 00000000000..29184fbf485 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFile.feature @@ -0,0 +1,334 @@ +@api +Feature: upload file + As a user + I want to be able to upload files + So that I can store and share files between multiple client systems + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @smokeTest + Scenario Outline: upload a file and check download content + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "" using the WebDAV API + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And the content of file "" for user "Alice" should be "uploaded content" + Examples: + | dav_version | file_name | + | old | /upload.txt | + | old | /नेपाली.txt | + | old | /strängé file.txt | + | old | /s,a,m,p,l,e.txt | + | new | /upload.txt | + | new | /नेपाली.txt | + | new | /strängé file.txt | + | new | /s,a,m,p,l,e.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | /upload.txt | + | spaces | /नेपाली.txt | + | spaces | /strängé file.txt | + | spaces | /s,a,m,p,l,e.txt | + + + Scenario Outline: upload a file and check download content + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to using the WebDAV API + Then the HTTP status code should be "201" + And the content of file for user "Alice" should be "uploaded content" + Examples: + | dav_version | file_name | + | old | "C++ file.cpp" | + | old | "file #2.txt" | + | new | "C++ file.cpp" | + | new | "file #2.txt" | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | "C++ file.cpp" | + | spaces | "file #2.txt" | + + @issue-ocis-reva-265 + #after fixing all issues delete this Scenario and merge with the one above + Scenario Outline: upload a file and check download content + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to using the WebDAV API + Then the HTTP status code should be "201" + And the content of file for user "Alice" should be "uploaded content" + Examples: + | dav_version | file_name | + | old | "file ?2.txt" | + | old | " ?fi=le&%#2 . txt" | + | old | " # %ab ab?=ed " | + | new | "file ?2.txt" | + | new | " ?fi=le&%#2 . txt" | + | new | " # %ab ab?=ed " | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | "file ?2.txt" | + | spaces | " ?fi=le&%#2 . txt" | + | spaces | " # %ab ab?=ed " | + + + Scenario Outline: upload a file with comma in the filename and check download content + Given using DAV path + When user "Alice" uploads file with content "file with comma" to using the WebDAV API + Then the HTTP status code should be "201" + And the content of file for user "Alice" should be "file with comma" + Examples: + | dav_version | file_name | + | old | "sample,1.txt" | + | old | ",,,.txt" | + | old | ",,,.," | + | new | "sample,1.txt" | + | new | ",,,.txt" | + | new | ",,,.," | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file_name | + | spaces | "sample,1.txt" | + | spaces | ",,,.txt" | + | spaces | ",,,.," | + + + Scenario Outline: upload a file into a folder and check download content + Given using DAV path + And user "Alice" has created folder "" + When user "Alice" uploads file with content "uploaded content" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/" for user "Alice" should be "uploaded content" + Examples: + | dav_version | folder_name | file_name | + | old | /upload | abc.txt | + | old | /strängé folder | strängé file.txt | + | old | /C++ folder | C++ file.cpp | + | old | /नेपाली | नेपाली | + | old | /folder #2.txt | file #2.txt | + | new | /upload | abc.txt | + | new | /strängé folder (duplicate #2 &) | strängé file (duplicate #2 &) | + | new | /C++ folder | C++ file.cpp | + | new | /नेपाली | नेपाली | + | new | /folder #2.txt | file #2.txt | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /strängé folder | strängé file.txt | + | spaces | /upload | abc.txt | + | spaces | /C++ folder | C++ file.cpp | + | spaces | /नेपाली | नेपाली | + | spaces | /folder #2.txt | file #2.txt | + + @issue-ocis-reva-265 + #after fixing all issues delete this Scenario and merge with the one above + Scenario Outline: upload a file into a folder and check download content + Given using DAV path + And user "Alice" has created folder "" + When user "Alice" uploads file with content "uploaded content" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And the content of file "/" for user "Alice" should be "uploaded content" + Examples: + | dav_version | folder_name | file_name | + | old | /folder ?2.txt | file ?2.txt | + | old | /?fi=le&%#2 . txt | # %ab ab?=ed | + | new | /folder ?2.txt | file ?2.txt | + | new | /?fi=le&%#2 . txt | # %ab ab?=ed | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /folder ?2.txt | file ?2.txt | + | spaces | /?fi=le&%#2 . txt | # %ab ab?=ed | + + + Scenario Outline: attempt to upload a file into a nonexistent folder + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "nonexistent-folder/new-file.txt" using the WebDAV API + Then the HTTP status code should be "409" + And as "Alice" folder "nonexistent-folder" should not exist + And as "Alice" file "nonexistent-folder/new-file.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-15 + Scenario Outline: Uploading file to path with extension .part should not be possible + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/textfile.part" using the WebDAV API + Then the HTTP status code should be "400" + And the DAV exception should be "OCA\DAV\Connector\Sabre\Exception\InvalidPath" + And the DAV message should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And the DAV reason should be "Can`t upload files with extension .part because these extensions are reserved for internal use." + And user "Alice" should not see the following elements + | /textfile.part | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file into a folder with dots in the path and check download content + Given using DAV path + And user "Alice" has created folder "" + When user "Alice" uploads file with content "uploaded content for file name ending with a dot" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "//" should exist + And the content of file "/" for user "Alice" should be "uploaded content for file name ending with a dot" + Examples: + | dav_version | folder_name | file_name | + | old | /upload. | abc. | + | old | /upload. | abc . | + | old | /upload.1 | abc.txt | + | old | /upload...1.. | abc...txt.. | + | old | /... | ... | + | new | /..upload | ..abc | + | new | /upload. | abc. | + | new | /upload. | abc . | + | new | /upload.1 | abc.txt | + | new | /upload...1.. | abc...txt.. | + | new | /... | ... | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /upload. | abc. | + | spaces | /upload. | abc . | + | spaces | /upload.1 | abc.txt | + | spaces | /upload...1.. | abc...txt.. | + | spaces | /... | ... | + + @issue-ocis-reva-174 + Scenario Outline: upload file with mtime + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "file.txt" should exist + And as "Alice" the mtime of the file "file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-174 + Scenario Outline: upload a file with mtime in a folder + Given using DAV path + And user "Alice" has created folder "testFolder" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/testFolder/file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/testFolder/file.txt" should exist + And as "Alice" the mtime of the file "/testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-174 + Scenario Outline: moving a file does not change its mtime + Given using DAV path + And user "Alice" has created folder "testFolder" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API + And user "Alice" moves file "file.txt" to "/testFolder/file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/testFolder/file.txt" should exist + And as "Alice" the mtime of the file "/testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-174 + Scenario Outline: overwriting a file changes its mtime + Given using DAV path + And user "Alice" has uploaded file with content "first time upload content" to "file.txt" + When user "Alice" uploads a file with content "Overwrite file" and mtime "Thu, 08 Aug 2019 04:18:13 GMT" to "file.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "file.txt" should exist + And as "Alice" the mtime of the file "file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + And the content of file "file.txt" for user "Alice" should be "Overwrite file" + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a hidden file and check download content + Given using DAV path + And user "Alice" has created folder "/FOLDER" + When user "Alice" uploads the following files with content "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Then the HTTP status code of responses on all endpoints should be "201" + And as "Alice" the following files should exist + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + And the content of the following files for user "Alice" should be "hidden file" + | path | + | .hidden_file | + | /FOLDER/.hidden_file | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file of size zero byte + Given using DAV path + When user "Alice" uploads file "filesForUpload/zerobyte.txt" to "/zerobyte.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "zerobyte.txt" should exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFileAsyncUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileAsyncUsingNewChunking.feature new file mode 100644 index 00000000000..ac8b0f3175c --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileAsyncUsingNewChunking.feature @@ -0,0 +1,184 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: upload file using new chunking + As a user + I want to be able to upload "large" files in chunks asynchronously + So that I do not have to wait for the long MOVE operation on assembly to finish + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And the owncloud log level has been set to debug + And the owncloud log has been cleared + And the administrator has enabled async operations + + + Scenario: Upload chunked file ordered asc using async MOVE + When user "Alice" uploads the following chunks asynchronously to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Upload chunked file ordered desc using async MOVE + When user "Alice" uploads the following chunks asynchronously to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 3 | CCCCC | + | 2 | BBBBB | + | 1 | AAAAA | + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Upload chunked file in random order using async MOVE + When user "Alice" uploads the following chunks asynchronously to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 2 | BBBBB | + | 3 | CCCCC | + | 1 | AAAAA | + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Upload chunked file overwriting existing file using async MOVE + Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has copied file "/textfile0.txt" to "/existingFile.txt" + When user "Alice" uploads the following chunks asynchronously to "/existingFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And the content of file "/existingFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: New chunked upload MOVE using old DAV path should fail + Given user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + When using old DAV path + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" using the WebDAV API + Then the HTTP status code should be "404" + + + Scenario: Upload file via new chunking endpoint with wrong size header using async MOVE + Given user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + When user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with size 5 using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^error$/ | + | errorCode | /^400$/ | + | errorMessage | /^Chunks on server do not sum up to 5 but to 15$/ | + + + Scenario: Upload file via new chunking endpoint with correct size header using async MOVE + Given user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + When user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/myChunkedFile.txt" with size 15 using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario Outline: Upload files with difficult names using new chunking and async MOVE + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/" using the WebDAV API + Then the HTTP status code should be "202" + And the following headers should match these regular expressions for user "Alice" + | OC-JobStatus-Location | /%base_path%\/remote\.php\/dav\/job-status\/%username%\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ | + And the oc job status values of last request for user "Alice" should match these regular expressions + | status | /^finished$/ | + | fileId | /^[0-9a-z]{20,}$/ | + And as "Alice" file "/" should exist + And the content of file "/" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + Examples: + | file-name | + | &#? | + | TIÄFÜ | + + + Scenario: disabled async operations leads to original behavior + Given the administrator has disabled async operations + When user "Alice" uploads the following chunks asynchronously to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And the following headers should not be set + | header | + | OC-JobStatus-Location | + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + + + Scenario: enabling async operations does no difference to normal MOVE - Upload chunked file + When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And the following headers should not be set + | header | + | OC-JobStatus-Location | + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature new file mode 100644 index 00000000000..3b67e27d8e3 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedName.feature @@ -0,0 +1,76 @@ +@api +Feature: users cannot upload a file to a blacklisted name + As an administrator + I want to be able to prevent users from uploading files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @issue-ocis-reva-15 @notToImplementOnOCIS + Scenario Outline: upload a file to a filename that is banned by default + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to ".htaccess" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".htaccess" should not exist + Examples: + | dav_version | + | old | + | new | + + @issue-ocis-reva-54 + Scenario Outline: upload a file to a banned filename + Given using DAV path + And the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" uploads file with content "uploaded content" to "blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "blacklisted-file.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-54 + Scenario Outline: upload a file to a filename that matches (or not) blacklisted_files_regex + Given using DAV path + And user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + And the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" uploads to these filenames with content "uploaded content" using the webDAV API then the results should be as listed + | filename | http-code | exists | + | .ext | 403 | no | + | filename.ext | 403 | no | + | bannedfilename.txt | 403 | no | + | containsbannedstring | 403 | no | + | this-ContainsBannedString.txt | 403 | no | + | /FOLDER/.ext | 403 | no | + | /FOLDER/filename.ext | 403 | no | + | /FOLDER/bannedfilename.txt | 403 | no | + | /FOLDER/containsbannedstring | 403 | no | + | /FOLDER/this-ContainsBannedString.txt | 403 | no | + | .extension | 201 | yes | + | filename.txt | 201 | yes | + | bannedfilename | 201 | yes | + | bannedfilenamewithoutdot | 201 | yes | + | not-contains-banned-string.txt | 201 | yes | + | /FOLDER/.extension | 201 | yes | + | /FOLDER/filename.txt | 201 | yes | + | /FOLDER/bannedfilename | 201 | yes | + | /FOLDER/bannedfilenamewithoutdot | 201 | yes | + | /FOLDER/not-contains-banned-string.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedNameAsyncUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedNameAsyncUsingNewChunking.feature new file mode 100644 index 00000000000..8705504a848 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToBlacklistedNameAsyncUsingNewChunking.feature @@ -0,0 +1,64 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: users cannot upload a file to a blacklisted name using new chunking + As an administrator + I want to be able to prevent users from uploading files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And the owncloud log level has been set to debug + And the owncloud log has been cleared + And the administrator has enabled async operations + + + Scenario: Upload to a filename that is banned by default using new chunking and async MOVE + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/.htaccess" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/.htaccess" should not exist + + + Scenario: Upload to a banned filename using new chunking and async MOVE + Given the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/blacklisted-file.txt" should not exist + + + Scenario Outline: upload a file to a filename that matches blacklisted_files_regex using new chunking and async MOVE + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "" should not exist + Examples: + | filename | + | filename.ext | + | bannedfilename.txt | + | this-ContainsBannedString.txt | + + + Scenario: upload a file to a filename that does not match blacklisted_files_regex using new chunking and async MOVE + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "not-contains-banned-string.txt" using the WebDAV API + Then the HTTP status code should be "202" + And as "Alice" file "not-contains-banned-string.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature new file mode 100644 index 00000000000..08ff53f1239 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectory.feature @@ -0,0 +1,86 @@ +@api +Feature: users cannot upload a file to or into an excluded directory + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to upload a file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @issue-ocis-reva-54 + Scenario Outline: upload a file to an excluded directory name + Given using DAV path + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file with content "uploaded content" to ".github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".github" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-54 + Scenario Outline: upload a file to an excluded directory name inside a parent directory + Given using DAV path + And user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file with content "uploaded content" to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/FOLDER" should exist + But as "Alice" file "/FOLDER/.github" should not exist + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @issue-ocis-reva-54 + Scenario Outline: upload a file to a filename that matches (or not) excluded_directories_regex + Given using DAV path + And user "Alice" has created folder "FOLDER" + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + And the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" uploads to these filenames with content "uploaded content" using the webDAV API then the results should be as listed + | filename | http-code | exists | + | endswith.bad | 403 | no | + | thisendswith.bad | 403 | no | + | .git | 403 | no | + | .github | 403 | no | + | containsvirusinthename | 403 | no | + | this-containsvirusinthename.txt | 403 | no | + | /FOLDER/endswith.bad | 403 | no | + | /FOLDER/thisendswith.bad | 403 | no | + | /FOLDER/.git | 403 | no | + | /FOLDER/.github | 403 | no | + | /FOLDER/containsvirusinthename | 403 | no | + | /FOLDER/this-containsvirusinthename.txt | 403 | no | + | endswith.badandotherstuff | 201 | yes | + | thisendswith.badandotherstuff | 201 | yes | + | name.git | 201 | yes | + | name.github | 201 | yes | + | not-contains-virus-in-the-name.txt | 201 | yes | + | /FOLDER/endswith.badandotherstuff | 201 | yes | + | /FOLDER/thisendswith.badandotherstuff | 201 | yes | + | /FOLDER/name.git | 201 | yes | + | /FOLDER/name.github | 201 | yes | + | /FOLDER/not-contains-virus-in-the-name.txt | 201 | yes | + Examples: + | dav_version | + | old | + | new | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectoryAsyncUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectoryAsyncUsingNewChunking.feature new file mode 100644 index 00000000000..7dbc847654f --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload1/uploadFileToExcludedDirectoryAsyncUsingNewChunking.feature @@ -0,0 +1,67 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: users cannot upload a file to or into an excluded directory using new chunking + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to upload a file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + And the owncloud log level has been set to debug + And the owncloud log has been cleared + And the administrator has enabled async operations + + + Scenario: Upload to an excluded directory name using new chunking and async MOVE + Given the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/.github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "/.github" should not exist + + + Scenario: Upload to an excluded directory name inside a parent directory using new chunking and async MOVE + Given user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/FOLDER" should exist + But as "Alice" file "/FOLDER/.github" should not exist + + + Scenario Outline: upload a file to a filename that matches excluded_directories_regex using new chunking and async MOVE + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "" should not exist + Examples: + | filename | + | thisendswith.bad | + | .github | + | this-containsvirusinthename.txt | + + + Scenario: upload a file to a filename that does not match excluded_directories_regex using new chunking and async MOVE + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" asynchronously to "not-contains-virus-in-the-name.txt" using the WebDAV API + Then the HTTP status code should be "202" + And as "Alice" file "not-contains-virus-in-the-name.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingNewChunking.feature new file mode 100644 index 00000000000..7e02f174cd2 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingNewChunking.feature @@ -0,0 +1,62 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: users cannot upload a file to a blacklisted name using new chunking + As an administrator + I want to be able to prevent users from uploading files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using OCS API version "1" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Upload a file to a filename that is banned by default using new chunking + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to ".htaccess" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".htaccess" should not exist + + + Scenario: Upload a file to a banned filename using new chunking + Given the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "blacklisted-file.txt" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "blacklisted-file.txt" should not exist + + + Scenario Outline: upload a file to a filename that matches blacklisted_files_regex using new chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "" should not exist + Examples: + | filename | + | filename.ext | + | bannedfilename.txt | + | this-ContainsBannedString.txt | + + + Scenario: upload a file to a filename that does not match blacklisted_files_regex using new chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "not-contains-banned-string.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "not-contains-banned-string.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature new file mode 100644 index 00000000000..57181e3b5f3 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature @@ -0,0 +1,46 @@ +@api @issue-ocis-reva-15 +Feature: users cannot upload a file to a blacklisted name using old chunking + As an administrator + I want to be able to prevent users from uploading files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + + @skipOnOcV10 @issue-36645 @notToImplementOnOCIS + Scenario: Upload a file to a filename that is banned by default using old chunking + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/.htaccess" in 3 chunks using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".htaccess" should not exist + + @skipOnOcV10 @issue-36645 + Scenario: Upload a file to a banned filename using old chunking + Given the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "blacklisted-file.txt" in 3 chunks using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "blacklisted-file.txt" should not exist + + @skipOnOcV10 @issue-36645 + Scenario Outline: upload a file to a filename that matches blacklisted_files_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "" in 3 chunks using the WebDAV API + Then the HTTP status code should be "" + And as "Alice" file "" should not exist + Examples: + | filename | http-status | + | filename.ext | 403 | + | bannedfilename.txt | 403 | + | this-ContainsBannedString.txt | 403 | + + + Scenario: upload a file to a filename that does not match blacklisted_files_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "not-contains-banned-string.txt" in 3 chunks using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "not-contains-banned-string.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunkingOc10Issue36645.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunkingOc10Issue36645.feature new file mode 100644 index 00000000000..9f0135c5ed7 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunkingOc10Issue36645.feature @@ -0,0 +1,39 @@ +@notToImplementOnOCIS @api @issue-ocis-reva-15 +Feature: users cannot upload a file to a blacklisted name using old chunking + As an administrator + I want to be able to prevent users from uploading files to specified file names + So that I can prevent unwanted file names existing in the cloud storage + + Background: + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + + @issue-36645 + Scenario: Upload a file to a filename that is banned by default using old chunking + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/.htaccess" in 3 chunks using the WebDAV API + Then the HTTP status code should be "507" +# Then the HTTP status code should be "403" + And as "Alice" file ".htaccess" should not exist + + @issue-36645 + Scenario: Upload a file to a banned filename using old chunking + Given the administrator has updated system config key "blacklisted_files" with value '["blacklisted-file.txt",".htaccess"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "blacklisted-file.txt" in 3 chunks using the WebDAV API + Then the HTTP status code should be "507" +# Then the HTTP status code should be "403" + And as "Alice" file "blacklisted-file.txt" should not exist + + @issue-36645 + Scenario Outline: upload a file to a filename that matches blacklisted_files_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being .*\.ext$ and ^bannedfilename\..+ + Given the administrator has updated system config key "blacklisted_files_regex" with value '[".*\\.ext$","^bannedfilename\\..+","containsbannedstring"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "" in 3 chunks using the WebDAV API + Then the HTTP status code should be "" + And as "Alice" file "" should not exist + Examples: + | filename | http-status | comment | + | filename.ext | 507 | issue-36645 | + | bannedfilename.txt | 403 | ok | + | this-ContainsBannedString.txt | 403 | ok | diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingNewChunking.feature new file mode 100644 index 00000000000..accfd7aa1f3 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingNewChunking.feature @@ -0,0 +1,65 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: users cannot upload a file to or into an excluded directory using new chunking + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to upload a file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Upload a file to an excluded directory name using new chunking + Given the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to ".github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".github" should not exist + + + Scenario: Upload a file to an excluded directory name inside a parent directory using new chunking + Given user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/FOLDER/.github" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/FOLDER" should exist + But as "Alice" file "/FOLDER/.github" should not exist + + + Scenario Outline: upload a file to a filename that matches excluded_directories_regex using new chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "" using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file "" should not exist + Examples: + | filename | + | thisendswith.bad | + | .github | + | this-containsvirusinthename.txt | + + + Scenario: upload a file to a filename that does not match excluded_directories_regex using new chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "not-contains-virus-in-the-name.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "not-contains-virus-in-the-name.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature new file mode 100644 index 00000000000..b86c037efe8 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature @@ -0,0 +1,49 @@ +@api @issue-ocis-reva-15 +Feature: users cannot upload a file to or into an excluded directory using old chunking + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to upload a file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + + @skipOnOcV10 @issue-36645 + Scenario: Upload a file to an excluded directory name using old chunking + Given the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/.github" in 3 chunks using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" file ".github" should not exist + + @skipOnOcV10 @issue-36645 + Scenario: Upload a file to an excluded directory name inside a parent directory using old chunking + Given user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/FOLDER/.github" in 3 chunks using the WebDAV API + Then the HTTP status code should be "403" + And as "Alice" folder "/FOLDER" should exist + But as "Alice" file "/FOLDER/.github" should not exist + + @skipOnOcV10 @issue-36645 + Scenario Outline: upload a file to a filename that matches excluded_directories_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "" in 3 chunks using the WebDAV API + Then the HTTP status code should be "" + And as "Alice" file "" should not exist + Examples: + | filename | http-status | + | thisendswith.bad | 503 | + | .github | 403 | + | this-containsvirusinthename.txt | 403 | + + + Scenario: upload a file to a filename that does not match excluded_directories_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "not-contains-virus-in-the-name.txt" in 3 chunks using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "not-contains-virus-in-the-name.txt" should exist diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunkingOc10Issue36645.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunkingOc10Issue36645.feature new file mode 100644 index 00000000000..34a74b048d6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunkingOc10Issue36645.feature @@ -0,0 +1,42 @@ +@notToImplementOnOCIS @api @issue-ocis-reva-15 +Feature: users cannot upload a file to or into an excluded directory using old chunking + As an administrator + I want to be able to exclude directories (folders) from being processed. Any attempt to upload a file to one of those names should be refused. + So that I can have directories on my cloud server storage that are not available for syncing. + + Background: + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + + @issue-36645 + Scenario: Upload a file to an excluded directory name using old chunking + Given the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/.github" in 3 chunks using the WebDAV API + Then the HTTP status code should be "507" +# Then the HTTP status code should be "403" + And as "Alice" file ".github" should not exist + + @issue-36645 + Scenario: Upload a file to an excluded directory name inside a parent directory using old chunking + Given user "Alice" has created folder "FOLDER" + And the administrator has updated system config key "excluded_directories" with value '[".github"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/FOLDER/.github" in 3 chunks using the WebDAV API + Then the HTTP status code should be "507" +# Then the HTTP status code should be "403" + And as "Alice" folder "/FOLDER" should exist + But as "Alice" file "/FOLDER/.github" should not exist + + @issue-36645 + Scenario Outline: upload a file to a filename that matches excluded_directories_regex using old chunking + # Note: we have to write JSON for the value, and to get a backslash in the double-quotes we have to escape it + # The actual regular expressions end up being endswith\.bad$ and ^\.git + Given the administrator has updated system config key "excluded_directories_regex" with value '["endswith\\.bad$","^\\.git","containsvirusinthename"]' and type "json" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "" in 3 chunks using the WebDAV API + Then the HTTP status code should be "" + And as "Alice" file "" should not exist + Examples: + | filename | http-status | comment | + | thisendswith.bad | 507 | issue-36645 | + | .github | 403 | ok | + | this-containsvirusinthename.txt | 403 | ok | diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingNewChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingNewChunking.feature new file mode 100644 index 00000000000..69419021364 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingNewChunking.feature @@ -0,0 +1,193 @@ +@api @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321 +Feature: upload file using new chunking + As a user + I want to be able to upload "large" files in chunks + So that the upload can be completed in less elapsed time + + Background: + Given using OCS API version "1" + And using new DAV path + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario: Upload chunked file asc with new chunking + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Upload chunked file desc with new chunking + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Upload chunked file random with new chunking + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: Checking file id after a move overwrite using new chunking endpoint + Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And the owncloud log level has been set to debug + And the owncloud log has been cleared + And user "Alice" has copied file "/textfile0.txt" to "/existingFile.txt" + And user "Alice" has stored id of file "/existingFile.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/existingFile.txt" in 3 chunks with new chunking and using the WebDAV API + Then the HTTP status code should be "204" + And user "Alice" file "/existingFile.txt" should have the previously stored id + And the content of file "/existingFile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + + Scenario: New chunked upload MKDIR using old DAV path should fail + Given using old DAV path + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + Then the HTTP status code should be "409" + + + Scenario: New chunked upload PUT using old DAV path should fail + Given user "Alice" has created a new chunking upload with id "chunking-42" + When using old DAV path + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + Then the HTTP status code should be "409" + + + Scenario: New chunked upload MOVE using old DAV path should fail + Given user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + When using old DAV path + And user "Alice" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" using the WebDAV API + Then the HTTP status code should be "404" + + + Scenario: Upload to new DAV path using old way should fail + When user "Alice" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt" using the WebDAV API + Then the HTTP status code should be "503" + + + Scenario: Upload file via new chunking endpoint with wrong size header + Given user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + When user "Alice" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with size 5 using the WebDAV API + Then the HTTP status code should be "400" + + + Scenario: Upload file via new chunking endpoint with correct size header + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + And user "Alice" has created a new chunking upload with id "chunking-42" + And user "Alice" has uploaded new chunk file "1" with "AAAAA" to id "chunking-42" + And user "Alice" has uploaded new chunk file "2" with "BBBBB" to id "chunking-42" + And user "Alice" has uploaded new chunk file "3" with "CCCCC" to id "chunking-42" + When user "Alice" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with size 15 using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + + @smokeTest + # This smokeTest scenario does ordinary checks for chunked upload, + # without adjusting the log level. This allows it to run in test environments + # where the log level has been fixed and cannot be changed. + Scenario Outline: Upload files with difficult names using new chunking + When user "Alice" creates a new chunking upload with id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "1" with "AAAAA" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "2" with "BBBBB" to id "chunking-42" using the WebDAV API + And user "Alice" uploads new chunk file "3" with "CCCCC" to id "chunking-42" using the WebDAV API + And user "Alice" moves new chunk file with id "chunking-42" to "/" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And the content of file "/" for user "Alice" should be "AAAAABBBBBCCCCC" + Examples: + | file-name | + | 0 | + | &#? | + | TIÄFÜ | + + + # This scenario does extra checks with the log level set to debug. + # It does not run in smoke test runs. (see comments in scenario above) + Scenario Outline: Upload files with difficult names using new chunking and check the log + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/" in 3 chunks with new chunking and using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And the content of file "/" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + Examples: + | file-name | + | &#? | + | TIÄFÜ | + + + Scenario: Upload chunked file with new chunking with lengthy filenames + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "हजार नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-012345.txt" with new chunking and using the WebDAV API + | number | content | + | 1 | AAAAAAAAAAAAAAAAAAAAAAAAA | + | 2 | BBBBBBBBBBBBBBBBBBBBBBBBB | + | 3 | CCCCCCCCCCCCCCCCCCCCCCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" file "हजार नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-012345.txt" should exist + And the content of file "हजार नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-012345.txt" for user "Alice" should be "AAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature new file mode 100644 index 00000000000..938351d63b0 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunking.feature @@ -0,0 +1,190 @@ +@api @issue-ocis-reva-17 +Feature: upload file using old chunking + As a user + I want to be able to upload "large" files in chunks + So that the upload can be completed in less elapsed time + + Background: + Given using OCS API version "1" + And user "Alice" has been created with default attributes and without skeleton files + + @skipOnOcV10 @issue-36115 + Scenario Outline: Upload chunked file asc + Given using DAV path + When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + Examples: + | dav_version | + | old | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Upload chunked file desc + Given using DAV path + When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API + | number | content | + | 3 | CCCCC | + | 2 | BBBBB | + | 1 | AAAAA | + Then the HTTP status code should be "201" + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Upload chunked file random + Given using DAV path + When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API + | number | content | + | 2 | BBBBB | + | 3 | CCCCC | + | 1 | AAAAA | + Then the HTTP status code should be "201" + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Checking file id after a move overwrite using old chunking endpoint + Given using DAV path + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And the owncloud log level has been set to debug + And the owncloud log has been cleared + And user "Alice" has copied file "/textfile0.txt" to "/existingFile.txt" + And user "Alice" has stored id of file "/existingFile.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/existingFile.txt" in 3 chunks with old chunking and using the WebDAV API + Then the HTTP status code should be "201" + And user "Alice" file "/existingFile.txt" should have the previously stored id + And the content of file "/existingFile.txt" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + Examples: + | dav_version | + | old | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | + | spaces | + + @smokeTest + # This smokeTest scenario does ordinary checks for chunked upload, + # without adjusting the log level. This allows it to run in test environments + # where the log level has been fixed and cannot be changed. + Scenario Outline: Chunked upload files with difficult name + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/" in 3 chunks using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And the content of file "/" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + Examples: + | dav_version | file-name | + | old | &#? TIÄFÜ @a#8a=b?c=d ?abc=oc # | + | old | 0 | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file-name | + | spaces | &#? TIÄFÜ @a#8a=b?c=d ?abc=oc # | + | spaces | 0 | + + # This scenario does extra checks with the log level set to debug. + # It does not run in smoke test runs. (see comments in scenario above) + Scenario Outline: Chunked upload files with difficult name and check the log + Given using DAV path + And the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/" in 3 chunks using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "/" should exist + And the content of file "/" for user "Alice" should be: + """ + This is a testfile. + + Cheers. + """ + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + Examples: + | dav_version | file-name | + | old | file-name | + | old | &#? | + | old | TIÄFÜ | + | old | 0 | + | old | @a#8a=b?c=d | + | old | ?abc=oc # | + + @skipOnOcV10 @personalSpace + Examples: + | dav_version | file-name | + | spaces | file-name | + | spaces | &#? | + | spaces | TIÄFÜ | + | spaces | 0 | + | spaces | @a#8a=b?c=d | + | spaces | ?abc=oc # | + + @skipOnOcV10 @issue-36115 + Scenario Outline: Upload chunked file with old chunking with lengthy filenames + Given using DAV path + Given the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" with old chunking and using the WebDAV API + | number | content | + | 1 | AAAAAAAAAAAAAAAAAAAAAAAAA | + | 2 | BBBBBBBBBBBBBBBBBBBBBBBBB | + | 3 | CCCCCCCCCCCCCCCCCCCCCCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" file "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" should exist + And the content of file "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" for user "Alice" should be "AAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | + Examples: + | dav_version | + | old | + + @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunkingOc10Issue36115.feature b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunkingOc10Issue36115.feature new file mode 100644 index 00000000000..a52fc8c0bba --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUpload2/uploadFileUsingOldChunkingOc10Issue36115.feature @@ -0,0 +1,43 @@ +@notToImplementOnOCIS @api @issue-ocis-reva-17 +Feature: upload file using old chunking + As a user + I want to be able to upload "large" files in chunks + So that the upload can be completed in less elapsed time + + @issue-36115 + Scenario: Upload chunked file asc + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + When user "Alice" uploads the following "3" chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API + | number | content | + | 1 | AAAAA | + | 2 | BBBBB | + | 3 | CCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^[a-f0-9:\.]{1,32}$/ | +# | ETag | /^"[a-f0-9:\.]{1,32}"$/ | + And as "Alice" file "/myChunkedFile.txt" should exist + And the content of file "/myChunkedFile.txt" for user "Alice" should be "AAAAABBBBBCCCCC" + + + Scenario: Upload chunked file with old chunking with lengthy filenames + Given using OCS API version "1" + And using old DAV path + And user "Alice" has been created with default attributes and without skeleton files + And the owncloud log level has been set to debug + And the owncloud log has been cleared + When user "Alice" uploads the following chunks to "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" with old chunking and using the WebDAV API + | number | content | + | 1 | AAAAAAAAAAAAAAAAAAAAAAAAA | + | 2 | BBBBBBBBBBBBBBBBBBBBBBBBB | + | 3 | CCCCCCCCCCCCCCCCCCCCCCCCC | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions for user "Alice" + | ETag | /^[a-f0-9:\.]{1,32}$/ | + And as "Alice" file "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" should exist + And the content of file "नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-नेपालि-file-नाम-12345678910.txt" for user "Alice" should be "AAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCC" + And the log file should not contain any log-entries containing these attributes: + | app | + | dav | \ No newline at end of file diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature new file mode 100644 index 00000000000..3eeb77f31dd --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature @@ -0,0 +1,291 @@ +@api @skipOnOcV10 +Feature: checksums + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Uploading a file with checksum should work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" uploads file with checksum "" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/textFile.txt" for user "Alice" should be "12345" + Examples: + | dav_version | checksum | + | old | MD5 827ccb0eea8a706c4c34a16891f84e7b | + | new | MD5 827ccb0eea8a706c4c34a16891f84e7b | + | old | SHA1 8cb2237d0679ca88db6464eac60da96345513964 | + | new | SHA1 8cb2237d0679ca88db6464eac60da96345513964 | + + @personalSpace + Examples: + | dav_version | checksum | + | spaces | MD5 827ccb0eea8a706c4c34a16891f84e7b | + | spaces | SHA1 8cb2237d0679ca88db6464eac60da96345513964 | + + + Scenario Outline: Uploading a file with checksum should return the checksum in the propfind + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" uploads file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e7b" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + And user "Alice" requests the checksum of "/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964 MD5:827ccb0eea8a706c4c34a16891f84e7b ADLER32:02f80100" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading a file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e7b" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + When user "Alice" downloads file "/textFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading a file with incorrect checksum should not work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" uploads file with checksum "" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "406" + And as "Alice" file "textFile.txt" should not exist + Examples: + | dav_version | incorrect_checksum | + | old | MD5 827ccb0eea8a706c4c34a16891f84e7a | + | new | MD5 827ccb0eea8a706c4c34a16891f84e7a | + | old | SHA1 8cb2237d0679ca88db6464eac60da96345513963 | + | new | SHA1 8cb2237d0679ca88db6464eac60da96345513963 | + + @personalSpace + Examples: + | dav_version | incorrect_checksum | + | spaces | MD5 827ccb0eea8a706c4c34a16891f84e7a | + | spaces | SHA1 8cb2237d0679ca88db6464eac60da96345513963 | + + + Scenario Outline: Uploading a chunked file with correct checksum should work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/textFile.txt" for user "Alice" should be "0123456789" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading a chunked file with correct checksum should return the checksum in the propfind + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + When user "Alice" requests the checksum of "/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:87acec17cd9dcd20a716cc2cf67417b71c8a7016 MD5:781e5e245d69b566979b86e28d23f2c7 ADLER32:0aff020e" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading a chunked file with checksum should return the checksum in the download header + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + When user "Alice" downloads file "/textFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:87acec17cd9dcd20a716cc2cf67417b71c8a7016" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading second chunk of file with incorrect checksum should not work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546799" using the TUS protocol on the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 781e5e245d69b566979b86e28d23f2c7" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "409" + And as "Alice" file "textFile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Uploading a file with correct checksum and overwriting an existing file should return the checksum for new data in the propfind + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + When user "Alice" overwrites existing file with offset "0" and data "hello" with checksum "" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 5 | + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" requests the checksum of "/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d MD5:5d41402abc4b2a76b9719d911017c592 ADLER32:062c0215" + And the content of file "/textFile.txt" for user "Alice" should be "hello" + Examples: + | dav_version | overwriteChecksum | + | old | MD5 5d41402abc4b2a76b9719d911017c592 | + | new | MD5 5d41402abc4b2a76b9719d911017c592 | + | old | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + | new | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + + @personalSpace + Examples: + | dav_version | overwriteChecksum | + | spaces | MD5 5d41402abc4b2a76b9719d911017c592 | + | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + + + Scenario Outline: Uploading a file with correct checksum and overwriting an existing file with invalid checksum should not work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" has uploaded a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + When user "Alice" overwrites existing file with offset "0" and data "hello" with checksum "" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 5 | + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + Then the HTTP status code should be "406" + And the content of file "/textFile.txt" for user "Alice" should be "0123456789" + Examples: + | dav_version | overwriteInvalidChecksum | + | old | MD5 5d41402abc4b2a76b9719d911017c593 | + | new | MD5 5d41402abc4b2a76b9719d911017c593 | + | old | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | + | new | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | + + @personalSpace + Examples: + | dav_version | overwriteInvalidChecksum | + | spaces | MD5 5d41402abc4b2a76b9719d911017c593 | + | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | + + + Scenario Outline: Overwriting an existing file with new data and checksum should return the checksum of new data in the propfind + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e7b" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + When user "Alice" overwrites existing file with offset "0" and data "hello" with checksum "" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" requests the checksum of "/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d MD5:5d41402abc4b2a76b9719d911017c592 ADLER32:062c0215" + And the content of file "/textFile.txt" for user "Alice" should be "hello" + Examples: + | dav_version | overwriteChecksum | + | old | MD5 5d41402abc4b2a76b9719d911017c592 | + | new | MD5 5d41402abc4b2a76b9719d911017c592 | + | old | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + | new | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + + @personalSpace + Examples: + | dav_version | overwriteChecksum | + | spaces | MD5 5d41402abc4b2a76b9719d911017c592 | + | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + + + Scenario Outline: Overwriting an existing file with new data and invalid checksum should not work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e7b" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + When user "Alice" overwrites existing file with offset "0" and data "hello" with checksum "" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + Then the HTTP status code should be "406" + And the content of file "/textFile.txt" for user "Alice" should be "12345" + Examples: + | dav_version | overwriteChecksum | + | old | MD5 5d41402abc4b2a76b9719d911017c593 | + | new | MD5 5d41402abc4b2a76b9719d911017c593 | + | old | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | + | new | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | + + @personalSpace + Examples: + | dav_version | overwriteChecksum | + | spaces | MD5 5d41402abc4b2a76b9719d911017c593 | + | spaces | SHA1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434a | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature new file mode 100644 index 00000000000..8a8c57e6091 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature @@ -0,0 +1,46 @@ +@api @skipOnOcV10 +Feature: tests of the creation extension see https://tus.io/protocols/resumable-upload.html#creation-with-upload + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: creating a new upload resource using creation with upload extension + Given using DAV path + When user "Alice" creates a new TUS resource with content "uploaded content" on the WebDAV API with these headers: + | Upload-Length | 16 | + | Tus-Resumable | 1.0.0 | + | Content-Type | application/offset+octet-stream | + # dGVzdC50eHQ= is the base64 encode of test.txt + | Upload-Metadata | filename dGVzdC50eHQ= | + | Tus-Extension | creation-with-upload | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions + | Tus-Resumable | /1\.0\.0/ | + | Location | /http[s]?:\/\/.*:\d+\/data\/.*/ | + | Upload-Offset | /\d+/ | + And the content of file "/test.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: creating a new resource and upload data in multiple bytes using creation with upload extension + Given using DAV path + When user "Alice" creates file "textFile.txt" and uploads content "12345" in the same request using the TUS protocol on the WebDAV API + Then the content of file "/textFile.txt" for user "Alice" should be "12345" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature new file mode 100644 index 00000000000..3340ed9eead --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelCreationExtension.feature @@ -0,0 +1,48 @@ +@api @skipOnOcV10 +Feature: low level tests of the creation extension see https://tus.io/protocols/resumable-upload.html#creation + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: creating a new upload resource + Given using DAV path + When user "Alice" creates a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 100 | + # d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg== is the base64 encode of world_domination_plan.pdf + | Upload-Metadata | filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg== | + | Tus-Resumable | 1.0.0 | + Then the HTTP status code should be "201" + And the following headers should match these regular expressions + | Tus-Resumable | /1\.0\.0/ | + | Location | /http[s]?:\/\/.*:\d+\/data\/.*/ | + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: creating a new upload resource without upload length + Given using DAV path + When user "Alice" creates a new TUS resource on the WebDAV API with these headers: + | Tus-Resumable | 1.0.0 | + # d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg== is the base64 encode of world_domination_plan.pdf + | Upload-Metadata | filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg== | + Then the HTTP status code should be "412" + And the following headers should not be set + | header | + | Location | + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature new file mode 100644 index 00000000000..02fba4390d6 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/lowLevelUpload.feature @@ -0,0 +1,90 @@ +@api @skipOnOcV10 +Feature: low level tests for upload of chunks + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: upload a chunk twice + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # ZmlsZS50eHQ= is the base64 encode of file.txt + | Upload-Metadata | filename ZmlsZS50eHQ= | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "000" using the WebDAV API + Then the HTTP status code should be "409" + And as "Alice" file "file.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: finalize file upload after uploading a chunk twice + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # ZmlsZS50eHQ= is the base64 encode of file.txt + | Upload-Metadata | filename ZmlsZS50eHQ= | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "000" using the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "4567890" using the WebDAV API + Then the HTTP status code should be "204" + And the content of file "/file.txt" for user "Alice" should be "1234567890" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: send last chunk twice + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # ZmlsZS50eHQ= is the base64 encode of file.txt + | Upload-Metadata | filename ZmlsZS50eHQ= | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "4567890" using the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" using the WebDAV API + Then the HTTP status code should be "404" + And the content of file "/file.txt" for user "Alice" should be "1234567890" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: start with uploading not at the beginning of the file + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # ZmlsZS50eHQ= is the base64 encode of file.txt + | Upload-Metadata | filename ZmlsZS50eHQ= | + When user "Alice" sends a chunk to the last created TUS Location with offset "1" and data "123" using the WebDAV API + Then the HTTP status code should be "409" + And as "Alice" file "file.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature new file mode 100644 index 00000000000..c41fddbd83b --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/optionsRequest.feature @@ -0,0 +1,115 @@ +@api @skipOnOcV10 +Feature: OPTIONS request + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario: send OPTIONS request to webDav endpoints using the TUS protocol with valid password and username + When user "Alice" requests these endpoints with "OPTIONS" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/ | + | /remote.php/dav/files/%username%/ | + Then the HTTP status code should be "204" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + @personalSpace + Scenario: send OPTIONS request to spaces webDav endpoint using the TUS protocol with valid password and username + When user "Alice" requests these endpoints with "OPTIONS" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/ | + Then the HTTP status code should be "204" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + + Scenario: send OPTIONS request to webDav endpoints using the TUS protocol without any authentication + When a user requests these endpoints with "OPTIONS" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/webdav/ | + | /remote.php/dav/files/%username%/ | + Then the HTTP status code should be "204" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + @personalSpace + Scenario: send OPTIONS request to spaces webDav endpoint using the TUS protocol without any authentication + When a user requests these endpoints with "OPTIONS" with body "doesnotmatter" and no authentication about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/ | + Then the HTTP status code should be "204" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + + Scenario: send OPTIONS request to webDav endpoints using the TUS protocol with valid username and wrong password + When user "Alice" requests these endpoints with "OPTIONS" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/webdav/ | + | /remote.php/dav/files/%username%/ | + Then the HTTP status code should be "401" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + @personalSpace + Scenario: send OPTIONS request to spaces webDav endpoint using the TUS protocol with valid username and wrong password + When user "Alice" requests these endpoints with "OPTIONS" including body "doesnotmatter" using password "invalid" about user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/ | + Then the HTTP status code should be "401" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + + Scenario: send OPTIONS requests to webDav endpoints using valid password and username of different user + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "OPTIONS" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/webdav/ | + | /remote.php/dav/files/%username%/ | + Then the HTTP status code should be "401" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | + + @personalSpace + Scenario: send OPTIONS requests to spaces webDav endpoints using valid password and username of different user + Given user "Brian" has been created with default attributes and without skeleton files + When user "Brian" requests these endpoints with "OPTIONS" including body "doesnotmatter" using the password of user "Alice" + | endpoint | + | /remote.php/dav/spaces/%spaceid%/ | + Then the HTTP status code should be "401" + And the following headers should be set + | header | value | + | Tus-Resumable | 1.0.0 | + | Tus-Version | 1.0.0 | + | Tus-Extension | creation,creation-with-upload,checksum,expiration | + | Tus-Checksum-Algorithm | md5,sha1,adler32 | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature new file mode 100644 index 00000000000..bea7de061fc --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature @@ -0,0 +1,211 @@ +@api @skipOnOcV10 +Feature: upload file + As a user + I want to be able to upload files + So that I can store and share files between multiple client systems + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: upload a file and check download content + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "" using the TUS protocol on the WebDAV API + Then the content of file "" for user "Alice" should be "uploaded content" + Examples: + | dav_version | file_name | + | old | /upload.txt | + | old | /नेपाली.txt | + | old | /strängé file.txt | + | old | /s,a,m,p,l,e.txt | + | old | /C++ file.cpp | + | old | /?fi=le&%#2 . txt | + | old | /# %ab ab?=ed | + | new | /upload.txt | + | new | /strängé file.txt | + | new | /नेपाली.txt | + | new | /s,a,m,p,l,e.txt | + | new | /C++ file.cpp | + | new | /?fi=le&%#2 . txt | + | new | /# %ab ab?=ed | + + @personalSpace + Examples: + | dav_version | file_name | + | spaces | /upload.txt | + | spaces | /strängé file.txt | + | spaces | /नेपाली.txt | + | spaces | /s,a,m,p,l,e.txt | + | spaces | /C++ file.cpp | + | spaces | /?fi=le&%#2 . txt | + | spaces | /# %ab ab?=ed | + + + Scenario Outline: upload a file into a folder and check download content + Given using DAV path + And user "Alice" has created folder "" + When user "Alice" uploads file with content "uploaded content" to "/" using the TUS protocol on the WebDAV API + Then the content of file "/" for user "Alice" should be "uploaded content" + Examples: + | dav_version | folder_name | file_name | + | old | /upload | abc.txt | + | old | /strängé folder | strängé file.txt | + | old | /C++ folder | C++ file.cpp | + | old | /नेपाली | नेपाली | + | old | /folder #2.txt | file #2.txt | + | old | /folder ?2.txt | file ?2.txt | + | old | /?fi=le&%#2 . txt | # %ab ab?=ed | + | new | /upload | abc.txt | + | new | /strängé folder (duplicate #2 &) | strängé file (duplicate #2 &) | + | new | /C++ folder | C++ file.cpp | + | new | /नेपाली | नेपाली | + | new | /folder #2.txt | file #2.txt | + | new | /folder ?2.txt | file ?2.txt | + | new | /?fi=le&%#2 . txt | # %ab ab?=ed | + + @personalSpace + Examples: + | dav_version | folder_name | file_name | + | spaces | /upload | abc.txt | + | spaces | /strängé folder (duplicate #2 &) | strängé file (duplicate #2 &) | + | spaces | /C++ folder | C++ file.cpp | + | spaces | /नेपाली | नेपाली | + | spaces | /folder #2.txt | file #2.txt | + | spaces | /folder ?2.txt | file ?2.txt | + | spaces | /?fi=le&%#2 . txt | # %ab ab?=ed | + + + Scenario Outline: Upload chunked file with TUS + Given using DAV path + When user "Alice" uploads file with content "uploaded content" in 3 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API + Then the content of file "/myChunkedFile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Upload 1 byte chunks with TUS + Given using DAV path + When user "Alice" uploads file with content "0123456789" in 10 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API + Then the content of file "/myChunkedFile.txt" for user "Alice" should be "0123456789" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: Upload to overwriting a file + Given using DAV path + And user "Alice" has uploaded file with content "original content" to "textfile.txt" + When user "Alice" uploads file with content "overwritten content" to "textfile.txt" using the TUS protocol on the WebDAV API + Then the content of file "textfile.txt" for user "Alice" should be "overwritten content" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file and no version is available + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "/upload.txt" using the TUS protocol on the WebDAV API + Then the version folder of file "/upload.txt" for user "Alice" should contain "0" elements + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file twice and versions are available + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "/upload.txt" using the TUS protocol on the WebDAV API + And user "Alice" uploads file with content "re-uploaded content" to "/upload.txt" using the TUS protocol on the WebDAV API + Then the version folder of file "/upload.txt" for user "Alice" should contain "1" element + And the content of file "/upload.txt" for user "Alice" should be "re-uploaded content" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file in chunks with TUS and no version is available + Given using DAV path + When user "Alice" uploads file with content "0123456789" in 10 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API + Then the version folder of file "/myChunkedFile.txt" for user "Alice" should contain "0" elements + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: upload a twice file in chunks with TUS and versions are available + Given using DAV path + When user "Alice" uploads file with content "0123456789" in 10 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API + And user "Alice" uploads file with content "01234" in 5 chunks to "/myChunkedFile.txt" using the TUS protocol on the WebDAV API + Then the version folder of file "/myChunkedFile.txt" for user "Alice" should contain "1" elements + And the content of file "/myChunkedFile.txt" for user "Alice" should be "01234" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file with invalid-name + Given using DAV path + When user "Alice" creates a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 100 | + | Upload-Metadata | filename | + | Tus-Resumable | 1.0.0 | + Then the HTTP status code should be "412" + And the following headers should not be set + | header | + | Location | + And as "Alice" file should not exist + Examples: + | dav_version | file_name | metadata | + | old | " " | IA== | + | old | "filewithLF-and-CR\r\n" | ZmlsZXdpdGhMRi1hbmQtQ1INCgo= | + | old | "folder/file" | Zm9sZGVyL2ZpbGU= | + | old | "my\\file" | bXkMaWxl | + | new | " " | IA== | + | new | "filewithLF-and-CR\r\n" | ZmlsZXdpdGhMRi1hbmQtQ1INCgo= | + | new | "folder/file" | Zm9sZGVyL2ZpbGU= | + | new | "my\\file" | bXkMaWxl | + + @personalSpace + Examples: + | dav_version | file_name | metadata | + | spaces | " " | IA== | + | spaces | "filewithLF-and-CR\r\n" | ZmlsZXdpdGhMRi1hbmQtQ1INCgo= | + | spaces | "folder/file" | Zm9sZGVyL2ZpbGU= | + | spaces | "my\\file" | bXkMaWxl | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature new file mode 100644 index 00000000000..c54b451f691 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature @@ -0,0 +1,70 @@ +@api @skipOnOcV10 +Feature: upload file + As a user + I want the mtime of an uploaded file to be the creation date on upload source not the upload date + So that I can find files by their real creation date + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: upload file with mtime + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload file with future mtime + Given using DAV path + When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2129 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "file.txt" should be "Thu, 08 Aug 2129 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: upload a file with mtime in a folder + Given using DAV path + And user "Alice" has created folder "testFolder" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/testFolder/file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "/testFolder/file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: overwriting a file changes its mtime + Given using DAV path + And user "Alice" has uploaded file with content "first time upload content" to "file.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "file.txt" should be "Thu, 08 Aug 2019 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature new file mode 100644 index 00000000000..24772cfa410 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature @@ -0,0 +1,71 @@ +@api @files_sharing-app-required @skipOnOcV10 +Feature: upload file + As a user + I want the mtime of an uploaded file to be the creation date on upload source not the upload date + So that I can find files by their real creation date + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: upload file with mtime to a received share + Given using DAV path + And user "Alice" has created folder "/toShare" + And user "Alice" has shared folder "/toShare" with user "Brian" + And user "Brian" has accepted share "/toShare" offered by user "Alice" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/toShare/file.txt" with mtime "Thu, 08 Aug 2012 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + And as "Brian" the mtime of the file "/Shares/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: upload file with mtime to a send share + Given using DAV path + And user "Alice" has created folder "/toShare" + And user "Alice" has shared folder "/toShare" with user "Brian" + And user "Brian" has accepted share "/toShare" offered by user "Alice" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/toShare/file.txt" with mtime "Thu, 08 Aug 2012 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + And as "Brian" the mtime of the file "/Shares/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: overwriting a file with mtime in a received share + Given using DAV path + And user "Alice" has created folder "/toShare" + And user "Alice" has shared folder "/toShare" with user "Brian" + And user "Brian" has accepted share "/toShare" offered by user "Alice" + And user "Alice" has uploaded file with content "uploaded content" to "/toShare/file.txt" + When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/toShare/file.txt" with mtime "Thu, 08 Aug 2012 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + And as "Brian" the mtime of the file "/Shares/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: overwriting a file with mtime in a send share + Given using DAV path + And user "Alice" has created folder "/toShare" + And user "Alice" has shared folder "/toShare" with user "Brian" + And user "Brian" has accepted share "/toShare" offered by user "Alice" + And user "Brian" has uploaded file with content "uploaded content" to "/Shares/toShare/file.txt" + When user "Alice" uploads file "filesForUpload/textfile.txt" to "/toShare/file.txt" with mtime "Thu, 08 Aug 2012 04:18:13 GMT" using the TUS protocol on the WebDAV API + Then as "Alice" the mtime of the file "/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + And as "Brian" the mtime of the file "/Shares/toShare/file.txt" should be "Thu, 08 Aug 2012 04:18:13 GMT" + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature new file mode 100644 index 00000000000..99bbf7caba9 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature @@ -0,0 +1,28 @@ +@api @skipOnOcV10 +Feature: move folders + As a user + I want to be able to move and upload files/folders + So that I can organise my data structure + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: Uploading file into a moved folder + Given using DAV path + And user "Alice" has created folder "/test" + And user "Alice" has created folder "/test-moved" + And user "Alice" has moved folder "/test-moved" to "/test/test-moved" + When user "Alice" uploads file with content "uploaded content" to "/test/test-moved/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" file "/test/test-moved/textfile.txt" should exist + And the content of file "/test/test-moved/textfile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToNonExistingFolder.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToNonExistingFolder.feature new file mode 100644 index 00000000000..22fcf5d3862 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToNonExistingFolder.feature @@ -0,0 +1,67 @@ +@api @skipOnOcV10 +Feature: upload file + As a user + I want to try uploading files to a nonexistent folder + So that I can check if the uploading works in such case + + Background: + Given using OCS API version "1" + And the administrator has set the default folder for received shares to "Shares" + And user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: attempt to upload a file into a nonexistent folder inside shares + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" folder "/Shares/FOLDER/" should not exist + And as "Alice" file "/Shares/FOLDER/textfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: attempt to upload a file into a nonexistent folder + Given using DAV path + When user "Alice" uploads file with content "uploaded content" to "/nonExistentFolder/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" folder "/nonExistentFolder" should not exist + And as "Alice" file "/nonExistentFolder/textfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + @personalSpace + Examples: + | dav_version | + | spaces | + + + Scenario Outline: attempt to upload a file into a nonexistent folder within correctly received share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/nonExistentFolder/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Brian" folder "/Shares/FOLDER/nonExistentFolder" should not exist + And as "Brian" file "/Shares/FOLDER/nonExistentFolder/textfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: attempt to upload a file into a nonexistent folder within correctly received read only share + Given using DAV path + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/nonExistentFolder/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Brian" folder "/Shares/FOLDER/nonExistentFolder" should not exist + And as "Brian" file "/Shares/FOLDER/nonExistentFolder/textfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature new file mode 100644 index 00000000000..6e60e4a5b89 --- /dev/null +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature @@ -0,0 +1,296 @@ +@api @files_sharing-app-required @skipOnOcV10 + +Feature: upload file to shared folder + + Background: + Given the administrator has set the default folder for received shares to "Shares" + And auto-accept shares has been disabled + And these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario Outline: Uploading file to a received share folder + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" file "/FOLDER/textfile.txt" should exist + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Uploading file to a user read/write share folder works + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "change" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" file "/FOLDER/textfile.txt" should exist + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Uploading a file into a group share as share receiver + Given using DAV path + And group "grp1" has been created + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "FOLDER" with group "grp1" with permissions "change" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" file "/FOLDER/textfile.txt" should exist + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Overwrite file to a received share folder + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has uploaded file with content "original content" to "/FOLDER/textfile.txt" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "overwritten content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Alice" file "/FOLDER/textfile.txt" should exist + And the content of file "/FOLDER/textfile.txt" for user "Alice" should be "overwritten content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: attempt to upload a file into a folder within correctly received read only share + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" uploads file with content "uploaded content" to "/Shares/FOLDER/textfile.txt" using the TUS protocol on the WebDAV API + Then as "Brian" file "/Shares/FOLDER/textfile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Upload a file to shared folder with checksum should return the checksum in the propfind for sharee + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # L0ZPTERFUi90ZXh0RmlsZS50eHQ= is the base64 encode of /FOLDER/textFile.txt + | Upload-Metadata | filename L0ZPTERFUi90ZXh0RmlsZS50eHQ= | + And user "Alice" has uploaded file with checksum "SHA1 8cb2237d0679ca88db6464eac60da96345513964" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + When user "Brian" requests the checksum of "/Shares/FOLDER/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964 MD5:827ccb0eea8a706c4c34a16891f84e7b ADLER32:02f80100" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Upload a file to shared folder with checksum should return the checksum in the download header for sharee + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # L0ZPTERFUi90ZXh0RmlsZS50eHQ= is the base64 encode of /FOLDER/textFile.txt + | Upload-Metadata | filename L0ZPTERFUi90ZXh0RmlsZS50eHQ= | + And user "Alice" has uploaded file with checksum "SHA1 8cb2237d069ca88db6464eac60da96345513964" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + When user "Brian" downloads file "/Shares/FOLDER/textFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer shares a file with correct checksum should return the checksum in the propfind for sharee + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "SHA1 8cb2237d0679ca88db6464eac60da96345513964" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + And user "Alice" has shared file "/textFile.txt" with user "Brian" + And user "Brian" has accepted share "/textFile.txt" offered by user "Alice" + When user "Brian" requests the checksum of "/Shares/textFile.txt" via propfind + Then the HTTP status code should be "207" + And the webdav checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964 MD5:827ccb0eea8a706c4c34a16891f84e7b ADLER32:02f80100" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer shares a file with correct checksum should return the checksum in the download header for sharee + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "SHA1 8cb2237d0679ca88db6464eac60da96345513964" to the last created TUS Location with offset "0" and content "12345" using the TUS protocol on the WebDAV API + And user "Alice" has shared file "/textFile.txt" with user "Brian" + And user "Brian" has accepted share "/textFile.txt" offered by user "Alice" + When user "Brian" downloads file "/Shares/textFile.txt" using the WebDAV API + Then the HTTP status code should be "200" + And the header checksum should match "SHA1:8cb2237d0679ca88db6464eac60da96345513964" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharee uploads a file to a received share folder with correct checksum + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" creates a new TUS resource on the WebDAV API with these headers: + | Tus-Resumable | 1.0.0 | + | Upload-Length | 16 | + # L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 is the base64 encode of /Shares/FOLDER/textFile.txt + | Upload-Metadata | filename L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 | + And user "Brian" uploads file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e7b" to the last created TUS Location with offset "0" and content "uploaded content" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/FOLDER/textFile.txt" should exist + And the content of file "/FOLDER/textFile.txt" for user "Alice" should be "uploaded content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharee uploads a file to a received share folder with wrong checksum should not work + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + When user "Brian" creates a new TUS resource on the WebDAV API with these headers: + | Tus-Resumable | 1.0.0 | + | Upload-Length | 16 | + # L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 is the base64 encode of /Shares/FOLDER/textFile.txt + | Upload-Metadata | filename L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 | + And user "Brian" uploads file with checksum "MD5 827ccb0eea8a706c4c34a16891f84e8c" to the last created TUS Location with offset "0" and content "uploaded content" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "406" + And as "Alice" file "/FOLDER/textFile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer uploads a file to shared folder with wrong correct checksum should not work + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # L0ZPTERFUi90ZXh0RmlsZS50eHQ= is the base64 encode of /FOLDER/textFile.txt + | Upload-Metadata | filename L0ZPTERFUi90ZXh0RmlsZS50eHQ= | + When user "Alice" uploads file with checksum "SHA1 8cb2237d0679ca88db6464eac60da96345513954" to the last created TUS Location with offset "0" and content "uploaded content" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "406" + And as "Alice" file "/FOLDER/textFile.txt" should not exist + And as "Brian" file "/Shares/FOLDER/textFile.txt" should not exist + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer uploads a chunked file with correct checksum and share it with sharee should work + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 10 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Alice" sends a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + And user "Alice" shares file "textFile.txt" with user "Brian" using the sharing API + And user "Brian" accepts share "/textFile.txt" offered by user "Alice" using the sharing API + Then the HTTP status code should be "200" + And the content of file "/Shares/textFile.txt" for user "Brian" should be "0123456789" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharee uploads a chunked file with correct checksum to a received share folder should work + Given using DAV path + And user "Alice" has created folder "/FOLDER" + And user "Alice" has shared folder "/FOLDER" with user "Brian" + And user "Brian" has accepted share "/FOLDER" offered by user "Alice" + And user "Brian" creates a new TUS resource on the WebDAV API with these headers: + | Tus-Resumable | 1.0.0 | + | Upload-Length | 10 | + # L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 is the base64 encode of /Shares/FOLDER/textFile.txt + | Upload-Metadata | filename L1NoYXJlcy9GT0xERVIvdGV4dGZpbGUudHh0 | + When user "Brian" sends a chunk to the last created TUS Location with offset "0" and data "01234" with checksum "MD5 4100c4d44da9177247e44a5fc1546778" using the TUS protocol on the WebDAV API + And user "Brian" sends a chunk to the last created TUS Location with offset "5" and data "56789" with checksum "MD5 099ebea48ea9666a7da2177267983138" using the TUS protocol on the WebDAV API + Then the HTTP status code should be "204" + And as "Alice" file "/FOLDER/textFile.txt" should exist + And the content of file "/FOLDER/textFile.txt" for user "Alice" should be "0123456789" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and correct checksum + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 16 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "SHA1 c1dab0c0864b6ac9bdd3743a1408d679f1acd823" to the last created TUS Location with offset "0" and content "original content" using the TUS protocol on the WebDAV API + And user "Alice" has shared file "/textFile.txt" with user "Brian" + And user "Brian" has accepted share "/textFile.txt" offered by user "Alice" + When user "Brian" overwrites recently shared file with offset "0" and data "overwritten content" with checksum "SHA1 fe990d2686a0fc86004efc31f5bf2475a45d4905" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 19 | + # dGV4dEZpbGUudHh0 is the base64 encode of /Shares/textFile.txt + | Upload-Metadata | filename L1NoYXJlcy90ZXh0RmlsZS50eHQ= | + Then the HTTP status code should be "204" + And the content of file "/textFile.txt" for user "Alice" should be "overwritten content" + Examples: + | dav_version | + | old | + | new | + + + Scenario Outline: Sharer uploads a file with checksum and as a sharee overwrites the shared file with new data and invalid checksum + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 16 | + # dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt + | Upload-Metadata | filename dGV4dEZpbGUudHh0 | + And user "Alice" has uploaded file with checksum "SHA1 c1dab0c0864b6ac9bdd3743a1408d679f1acd823" to the last created TUS Location with offset "0" and content "original content" using the TUS protocol on the WebDAV API + And user "Alice" has shared file "/textFile.txt" with user "Brian" + And user "Brian" has accepted share "/textFile.txt" offered by user "Alice" + When user "Brian" overwrites recently shared file with offset "0" and data "overwritten content" with checksum "SHA1 fe990d2686a0fc86004efc31f5bf2475a45d4906" using the TUS protocol on the WebDAV API with these headers: + | Upload-Length | 19 | + # dGV4dEZpbGUudHh0 is the base64 encode of /Shares/textFile.txt + | Upload-Metadata | filename L1NoYXJlcy90ZXh0RmlsZS50eHQ= | + Then the HTTP status code should be "406" + And the content of file "/textFile.txt" for user "Alice" should be "original content" + Examples: + | dav_version | + | old | + | new | diff --git a/tests/acceptance/filesForUpload/'single'quotes.txt b/tests/acceptance/filesForUpload/'single'quotes.txt new file mode 100644 index 00000000000..96abcb4f020 --- /dev/null +++ b/tests/acceptance/filesForUpload/'single'quotes.txt @@ -0,0 +1,9 @@ +a file with a single quote i its name + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/data.tar.gz b/tests/acceptance/filesForUpload/data.tar.gz new file mode 100644 index 00000000000..05c74a335f0 Binary files /dev/null and b/tests/acceptance/filesForUpload/data.tar.gz differ diff --git a/tests/acceptance/filesForUpload/data.zip b/tests/acceptance/filesForUpload/data.zip new file mode 100644 index 00000000000..d79780eada8 Binary files /dev/null and b/tests/acceptance/filesForUpload/data.zip differ diff --git a/tests/acceptance/filesForUpload/davtest.txt b/tests/acceptance/filesForUpload/davtest.txt new file mode 100644 index 00000000000..09c3a96be2f --- /dev/null +++ b/tests/acceptance/filesForUpload/davtest.txt @@ -0,0 +1 @@ +Dav-Test \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/example.gif b/tests/acceptance/filesForUpload/example.gif new file mode 100644 index 00000000000..9639afcdb5f Binary files /dev/null and b/tests/acceptance/filesForUpload/example.gif differ diff --git a/tests/acceptance/filesForUpload/file_to_overwrite.txt b/tests/acceptance/filesForUpload/file_to_overwrite.txt new file mode 100644 index 00000000000..f45c3ef31ee --- /dev/null +++ b/tests/acceptance/filesForUpload/file_to_overwrite.txt @@ -0,0 +1 @@ +BLABLABLA diff --git a/tests/acceptance/filesForUpload/lorem-big.txt b/tests/acceptance/filesForUpload/lorem-big.txt new file mode 100644 index 00000000000..7c3df7df515 --- /dev/null +++ b/tests/acceptance/filesForUpload/lorem-big.txt @@ -0,0 +1,93 @@ +a big lorem file + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/lorem.txt b/tests/acceptance/filesForUpload/lorem.txt new file mode 100644 index 00000000000..9d3b7a654c1 --- /dev/null +++ b/tests/acceptance/filesForUpload/lorem.txt @@ -0,0 +1,7 @@ +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/new-'single'quotes.txt b/tests/acceptance/filesForUpload/new-'single'quotes.txt new file mode 100644 index 00000000000..603f2187aae --- /dev/null +++ b/tests/acceptance/filesForUpload/new-'single'quotes.txt @@ -0,0 +1,9 @@ +a file with a single quote i its name, but different to the original one + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/new-data.tar.gz b/tests/acceptance/filesForUpload/new-data.tar.gz new file mode 100644 index 00000000000..05c74a335f0 Binary files /dev/null and b/tests/acceptance/filesForUpload/new-data.tar.gz differ diff --git a/tests/acceptance/filesForUpload/new-data.zip b/tests/acceptance/filesForUpload/new-data.zip new file mode 100644 index 00000000000..d79780eada8 Binary files /dev/null and b/tests/acceptance/filesForUpload/new-data.zip differ diff --git a/tests/acceptance/filesForUpload/new-lorem-big.txt b/tests/acceptance/filesForUpload/new-lorem-big.txt new file mode 100644 index 00000000000..2c1c631e030 --- /dev/null +++ b/tests/acceptance/filesForUpload/new-lorem-big.txt @@ -0,0 +1,93 @@ +a big lorem file but different to the original one + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/new-lorem.txt b/tests/acceptance/filesForUpload/new-lorem.txt new file mode 100644 index 00000000000..b935db537dd --- /dev/null +++ b/tests/acceptance/filesForUpload/new-lorem.txt @@ -0,0 +1,9 @@ +lorem file that is different from the original one + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git "a/tests/acceptance/filesForUpload/new-str\303\244ng\303\251 filename (duplicate #2 &).txt" "b/tests/acceptance/filesForUpload/new-str\303\244ng\303\251 filename (duplicate #2 &).txt" new file mode 100644 index 00000000000..ac6c70ca53f --- /dev/null +++ "b/tests/acceptance/filesForUpload/new-str\303\244ng\303\251 filename (duplicate #2 &).txt" @@ -0,0 +1,9 @@ +a file with funny characters in the file name but different from the original one + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/simple.odt b/tests/acceptance/filesForUpload/simple.odt new file mode 100644 index 00000000000..c6b8b94bfcb Binary files /dev/null and b/tests/acceptance/filesForUpload/simple.odt differ diff --git a/tests/acceptance/filesForUpload/simple.pdf b/tests/acceptance/filesForUpload/simple.pdf new file mode 100644 index 00000000000..305e543db83 Binary files /dev/null and b/tests/acceptance/filesForUpload/simple.pdf differ diff --git "a/tests/acceptance/filesForUpload/str\303\244ng\303\251 filename (duplicate #2 &).txt" "b/tests/acceptance/filesForUpload/str\303\244ng\303\251 filename (duplicate #2 &).txt" new file mode 100644 index 00000000000..a771bf79ea6 --- /dev/null +++ "b/tests/acceptance/filesForUpload/str\303\244ng\303\251 filename (duplicate #2 &).txt" @@ -0,0 +1,9 @@ +a file with funny characters in the file name + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/testavatar.jpg b/tests/acceptance/filesForUpload/testavatar.jpg new file mode 100644 index 00000000000..fb9e54042cb Binary files /dev/null and b/tests/acceptance/filesForUpload/testavatar.jpg differ diff --git a/tests/acceptance/filesForUpload/testavatar.png b/tests/acceptance/filesForUpload/testavatar.png new file mode 100644 index 00000000000..bd9c3cb1e0d Binary files /dev/null and b/tests/acceptance/filesForUpload/testavatar.png differ diff --git a/tests/acceptance/filesForUpload/textfile.txt b/tests/acceptance/filesForUpload/textfile.txt new file mode 100644 index 00000000000..efffdeff159 --- /dev/null +++ b/tests/acceptance/filesForUpload/textfile.txt @@ -0,0 +1,3 @@ +This is a testfile. + +Cheers. \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/zerobyte.txt b/tests/acceptance/filesForUpload/zerobyte.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/acceptance/filesForUpload/zzzz-must-be-last-file-in-folder.txt b/tests/acceptance/filesForUpload/zzzz-must-be-last-file-in-folder.txt new file mode 100644 index 00000000000..dcc19d17f8a --- /dev/null +++ b/tests/acceptance/filesForUpload/zzzz-must-be-last-file-in-folder.txt @@ -0,0 +1,11 @@ +This must be the last skeleton file in this folder, when sorted alphabetically. +Tests for performing actions on the last file in the folder try to find and use +this file name. + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/filesForUpload/zzzz-zzzz-will-be-at-the-end-of-the-folder-when-uploaded.txt b/tests/acceptance/filesForUpload/zzzz-zzzz-will-be-at-the-end-of-the-folder-when-uploaded.txt new file mode 100644 index 00000000000..1db21f1fb9b --- /dev/null +++ b/tests/acceptance/filesForUpload/zzzz-zzzz-will-be-at-the-end-of-the-folder-when-uploaded.txt @@ -0,0 +1,11 @@ +This file has a name so it would be the last after the upload, when sorted alphabetically. +Tests for performing actions on the last file in the folder try to find and use +this file name. + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, +totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, +sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est +qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora +incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum +exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? \ No newline at end of file diff --git a/tests/acceptance/lint-expected-failures.sh b/tests/acceptance/lint-expected-failures.sh new file mode 100755 index 00000000000..a1f31ce4da0 --- /dev/null +++ b/tests/acceptance/lint-expected-failures.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +log_error() { + echo -e "\e[31m$1\e[0m" +} + +log_info() { + echo -e "\e[34m$1\e[0m" +} + +log_success() { + echo -e "\e[32m$1\e[0m" +} + +declare -A scenarioLines + +if [ -n "${EXPECTED_FAILURES_FILE}" ] +then + if [ -f "${EXPECTED_FAILURES_FILE}" ] + then + log_info "Checking expected failures in ${EXPECTED_FAILURES_FILE}" + else + log_error "Expected failures file ${EXPECTED_FAILURES_FILE} not found" + log_error "Check the setting of EXPECTED_FAILURES_FILE environment variable" + exit 1 + fi + FINAL_EXIT_STATUS=0 + # If the last line of the expected-failures file ends without a newline character + # then that line may not get processed by some of the bash code in this script + # So check that the last character in the file is a newline + if [ "$(tail -c1 "${EXPECTED_FAILURES_FILE}" | wc -l)" -eq 0 ] + then + log_error "Expected failures file ${EXPECTED_FAILURES_FILE} must end with a newline" + log_error "Put a newline at the end of the last line and try again" + FINAL_EXIT_STATUS=1 + fi + # Check the expected-failures file to ensure that the lines are self-consistent + # In most cases the features that are being run are in owncloud/core, + # so assume that by default. + FEATURE_FILE_REPO="owncloud/core" + FEATURE_FILE_PATH="tests/acceptance/features" + LINE_NUMBER=0 + while read -r INPUT_LINE + do + LINE_NUMBER=$(("$LINE_NUMBER" + 1)) + + # Ignore comment lines (starting with hash) + if [[ "${INPUT_LINE}" =~ ^# ]] + then + continue + fi + # A line of text in the feature file can be used to indicate that the + # features being run are actually from some other repo. For example: + # "The expected failures in this file are from features in the owncloud/ocis repo." + # Write a line near the top of the expected-failures file to "declare" this, + # overriding the default "owncloud/core" + FEATURE_FILE_SPEC_LINE_FOUND="false" + if [[ "${INPUT_LINE}" =~ features[[:blank:]]in[[:blank:]]the[[:blank:]]([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)[[:blank:]]repo ]]; then + FEATURE_FILE_REPO="${BASH_REMATCH[1]}" + log_info "Features are expected to be in the ${FEATURE_FILE_REPO} repo\n" + FEATURE_FILE_SPEC_LINE_FOUND="true" + fi + if [[ "${INPUT_LINE}" =~ repo[[:blank:]]in[[:blank:]]the[[:blank:]]([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)[[:blank:]]folder[[:blank:]]tree ]]; then + FEATURE_FILE_PATH="${BASH_REMATCH[1]}" + log_info "Features are expected to be in the ${FEATURE_FILE_PATH} folder tree\n" + FEATURE_FILE_SPEC_LINE_FOUND="true" + fi + if [[ $FEATURE_FILE_SPEC_LINE_FOUND == "true" ]]; then + continue + fi + # Match lines that have "- [someSuite/someName.feature:n]" pattern on start + # the part inside the brackets is the suite, feature and line number of the expected failure. + if [[ "${INPUT_LINE}" =~ ^-[[:space:]]\[([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+)] ]]; then + SUITE_SCENARIO_LINE="${BASH_REMATCH[1]}" + elif [[ + # report for lines like: " - someSuite/someName.feature:n" + "${INPUT_LINE}" =~ ^[[:space:]]*-[[:space:]][a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+[[:space:]]*$ || + # report for lines starting with: "[someSuite/someName.feature:n]" + "${INPUT_LINE}" =~ ^[[:space:]]*\[([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+)] + ]]; then + log_error "> Line ${LINE_NUMBER}: Not in the correct format." + log_error " + Actual Line : '${INPUT_LINE}'" + log_error " - Expected Format : '- [suite/scenario.feature:line_number](scenario_line_url)'" + FINAL_EXIT_STATUS=1 + continue + else + # otherwise, ignore the line + continue + fi + # Find the link in round-brackets that should be after the SUITE_SCENARIO_LINE + if [[ "${INPUT_LINE}" =~ \(([a-zA-Z0-9:/.#_-]+)\) ]]; then + ACTUAL_LINK="${BASH_REMATCH[1]}" + else + log_error "Line ${LINE_NUMBER}: ${INPUT_LINE} : Link is empty" + FINAL_EXIT_STATUS=1 + continue + fi + if [[ -n "${scenarioLines[${SUITE_SCENARIO_LINE}]:-}" ]]; + then + log_error "> Line ${LINE_NUMBER}: Scenario line ${SUITE_SCENARIO_LINE} is duplicated" + FINAL_EXIT_STATUS=1 + fi + scenarioLines[${SUITE_SCENARIO_LINE}]="exists" + OLD_IFS=${IFS} + IFS=':' + read -ra FEATURE_PARTS <<< "${SUITE_SCENARIO_LINE}" + IFS=${OLD_IFS} + SUITE_FEATURE="${FEATURE_PARTS[0]}" + FEATURE_LINE="${FEATURE_PARTS[1]}" + EXPECTED_LINK="https://github.com/${FEATURE_FILE_REPO}/blob/master/${FEATURE_FILE_PATH}/${SUITE_FEATURE}#L${FEATURE_LINE}" + if [[ "${ACTUAL_LINK}" != "${EXPECTED_LINK}" ]]; then + log_error "> Line ${LINE_NUMBER}: Link is not correct for ${SUITE_SCENARIO_LINE}" + log_error " + Actual link : ${ACTUAL_LINK}" + log_error " - Expected link : ${EXPECTED_LINK}" + FINAL_EXIT_STATUS=1 + fi + + done < "${EXPECTED_FAILURES_FILE}" +else + log_error "Environment variable EXPECTED_FAILURES_FILE must be defined to be the file to check" + exit 1 +fi + +if [ ${FINAL_EXIT_STATUS} == 1 ] +then + log_error "\nErrors were found in the expected failures file - see the messages above!" +else + log_success "\nNo problems were found in the expected failures file." +fi +exit ${FINAL_EXIT_STATUS} diff --git a/tests/acceptance/run.sh b/tests/acceptance/run.sh new file mode 100755 index 00000000000..5d1af389686 --- /dev/null +++ b/tests/acceptance/run.sh @@ -0,0 +1,672 @@ +#!/usr/bin/env bash +[[ "${DEBUG}" == "true" ]] && set -x + +# from http://stackoverflow.com/a/630387 +SCRIPT_PATH="`dirname \"$0\"`" # relative +SCRIPT_PATH="`( cd \"${SCRIPT_PATH}\" && pwd )`" # absolutized and normalized + +echo 'Script path: '${SCRIPT_PATH} + +# Allow optionally passing in the path to the behat program. +# This gives flexibility for callers that have installed their own behat +if [ -z "${BEHAT_BIN}" ] +then + BEHAT=${SCRIPT_PATH}/../../vendor-bin/behat/vendor/bin/behat +else + BEHAT=${BEHAT_BIN} +fi +BEHAT_TAGS_OPTION_FOUND=false + +if [ -n "${STEP_THROUGH}" ] +then + STEP_THROUGH_OPTION="--step-through" +fi + +# The following environment variables can be specified: +# +# ACCEPTANCE_TEST_TYPE - see "--type" description +# BEHAT_FEATURE - see "--feature" description +# BEHAT_FILTER_TAGS - see "--tags" description +# BEHAT_SUITE - see "--suite" description +# BEHAT_YML - see "--config" description +# RUN_PART and DIVIDE_INTO_NUM_PARTS - see "--part" description +# SHOW_OC_LOGS - see "--show-oc-logs" description +# TESTING_REMOTE_SYSTEM - see "--remote" description +# EXPECTED_FAILURES_FILE - a file that contains a list of the scenarios that are expected to fail + +if [ -n "${EXPECTED_FAILURES_FILE}" ] +then + # Check the expected-failures file + ${SCRIPT_PATH}/lint-expected-failures.sh + LINT_STATUS=$? + if [ ${LINT_STATUS} -ne 0 ] + then + echo "Error: expected failures file ${EXPECTED_FAILURES_FILE} is invalid" + exit ${LINT_STATUS} + fi +fi + +# Default to API tests +# Note: if a specific feature or suite is also specified, then the acceptance +# test type is deduced from the suite name, and this environment variable +# ACCEPTANCE_TEST_TYPE is overridden. +if [ -z "${ACCEPTANCE_TEST_TYPE}" ] +then + ACCEPTANCE_TEST_TYPE="api" +fi + +# Look for command line options for: +# -c or --config - specify a behat.yml to use +# --feature - specify a single feature to run +# --suite - specify a single suite to run +# --type - api or core-api - if no individual feature or suite is specified, then +# specify the type of acceptance tests to run. Default api. +# --tags - specify tags for scenarios to run (or not) +# --remote - the server under test is remote, so we cannot locally enable the +# testing app. We have to assume it is already enabled. +# --show-oc-logs - tail the ownCloud log after the test run +# --loop - loop tests for given number of times. Only use it for debugging purposes +# --part - run a subset of scenarios, need two numbers. +# first number: which part to run +# second number: in how many parts to divide the set of scenarios +# --step-through - pause after each test step + +# Command line options processed here will override environment variables that +# might have been set by the caller, or in the code above. +while [[ $# -gt 0 ]] +do + key="$1" + case ${key} in + -c|--config) + BEHAT_YML="$2" + shift + ;; + --feature) + BEHAT_FEATURE="$2" + shift + ;; + --suite) + BEHAT_SUITE="$2" + shift + ;; + --loop) + BEHAT_RERUN_TIMES="$2" + shift + ;; + --type) + # Lowercase the parameter value, so the user can provide "API", "CORE-API", etc + ACCEPTANCE_TEST_TYPE="${2,,}" + shift + ;; + --tags) + BEHAT_FILTER_TAGS="$2" + BEHAT_TAGS_OPTION_FOUND=true + shift + ;; + --part) + RUN_PART="$2" + DIVIDE_INTO_NUM_PARTS="$3" + if [ ${RUN_PART} -gt ${DIVIDE_INTO_NUM_PARTS} ] + then + echo "cannot run part ${RUN_PART} of ${DIVIDE_INTO_NUM_PARTS}" + exit 1 + fi + shift 2 + ;; + --step-through) + STEP_THROUGH_OPTION="--step-through" + ;; + *) + # A "random" parameter is presumed to be a feature file to run. + # Typically that will be specified at the end, or as the only + # parameter. + BEHAT_FEATURE="$1" + ;; + esac + shift +done + +# Set the language to "C" +# We want to have it all in english to be able to parse outputs +export LANG=C + +# Provide a default admin username and password. +# But let the caller pass them if they wish +if [ -z "${ADMIN_USERNAME}" ] +then + ADMIN_USERNAME="admin" +fi + +if [ -z "${ADMIN_PASSWORD}" ] +then + ADMIN_PASSWORD="admin" +fi + +export ADMIN_USERNAME +export ADMIN_PASSWORD + +if [ -z "${BEHAT_RERUN_TIMES}" ] +then + BEHAT_RERUN_TIMES=1 +fi + +# expected variables +# -------------------- +# $SUITE_FEATURE_TEXT - human readable which test to run +# $BEHAT_SUITE_OPTION - suite setting with "--suite" or empty if all suites have to be run +# $BEHAT_FEATURE - feature file, or empty +# $BEHAT_FILTER_TAGS - list of tags +# $BEHAT_TAGS_OPTION_FOUND +# $TEST_LOG_FILE +# $BEHAT - behat executable +# $BEHAT_YML +# +# set arrays +# --------------- +# $UNEXPECTED_FAILED_SCENARIOS array of scenarios that failed unexpectedly +# $UNEXPECTED_PASSED_SCENARIOS array of scenarios that passed unexpectedly (while running with expected-failures.txt) + +declare -a UNEXPECTED_FAILED_SCENARIOS +declare -a UNEXPECTED_PASSED_SCENARIOS +declare -a UNEXPECTED_BEHAT_EXIT_STATUSES + +function run_behat_tests() { + echo "Running ${SUITE_FEATURE_TEXT} tests tagged ${BEHAT_FILTER_TAGS}" | tee ${TEST_LOG_FILE} + + if [ "${REPLACE_USERNAMES}" == "true" ] + then + echo "Usernames and attributes in tests are being replaced:" + cat ${SCRIPT_PATH}/usernames.json + fi + + echo "Using behat config '${BEHAT_YML}'" + ${BEHAT} --colors --strict ${STEP_THROUGH_OPTION} -c ${BEHAT_YML} -f pretty ${BEHAT_SUITE_OPTION} --tags ${BEHAT_FILTER_TAGS} ${BEHAT_FEATURE} -v 2>&1 | tee -a ${TEST_LOG_FILE} + + BEHAT_EXIT_STATUS=${PIPESTATUS[0]} + + # remove nullbytes from the test log + TEMP_CONTENT=$(tr < ${TEST_LOG_FILE} -d '\000') + OLD_IFS="${IFS}" + IFS="" + echo ${TEMP_CONTENT} > ${TEST_LOG_FILE} + IFS="${OLD_IFS}" + + # Find the count of scenarios that passed + SCENARIO_RESULTS_COLORED=`grep -Ea '^[0-9]+[[:space:]]scenario(|s)[[:space:]]\(' ${TEST_LOG_FILE}` + SCENARIO_RESULTS=$(echo "${SCENARIO_RESULTS_COLORED}" | sed "s/\x1b[^m]*m//g") + if [ ${BEHAT_EXIT_STATUS} -eq 0 ] + then + # They (SCENARIO_RESULTS) all passed, so just get the first number. + # The text looks like "1 scenario (1 passed)" or "123 scenarios (123 passed)" + [[ ${SCENARIO_RESULTS} =~ ([0-9]+) ]] + SCENARIOS_THAT_PASSED=$((SCENARIOS_THAT_PASSED + BASH_REMATCH[1])) + else + # "Something went wrong" with the Behat run (non-zero exit status). + # If there were "ordinary" test fails, then we process that later. Maybe they are all "expected failures". + # But if there were steps in a feature file that are undefined, we want to fail immediately. + # So exit the tests and do not lint expected failures when undefined steps exist. + if [[ ${SCENARIO_RESULTS} == *"undefined"* ]] + then + echo -e "\033[31m Undefined steps: There were some undefined steps found." + exit 1 + fi + # If there were no scenarios in the requested suite or feature that match + # the requested combination of tags, then Behat exits with an error status + # and reports "No scenarios" in its output. + # This can happen, for example, when running core suites from an app and + # requesting some tag combination that does not happen frequently. Then + # sometimes there may not be any matching scenarios in one of the suites. + # In this case, consider the test has passed. + MATCHING_COUNT=`grep -ca '^No scenarios$' ${TEST_LOG_FILE}` + if [ ${MATCHING_COUNT} -eq 1 ] + then + echo "Information: no matching scenarios were found." + BEHAT_EXIT_STATUS=0 + else + # Find the count of scenarios that passed and failed + SCENARIO_RESULTS_COLORED=`grep -Ea '^[0-9]+[[:space:]]scenario(|s)[[:space:]]\(' ${TEST_LOG_FILE}` + SCENARIO_RESULTS=$(echo "${SCENARIO_RESULTS_COLORED}" | sed "s/\x1b[^m]*m//g") + if [[ ${SCENARIO_RESULTS} =~ [0-9]+[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+ ]] + then + # Some passed and some failed, we got the second and third numbers. + # The text looked like "15 scenarios (6 passed, 9 failed)" + SCENARIOS_THAT_PASSED=$((SCENARIOS_THAT_PASSED + BASH_REMATCH[1])) + SCENARIOS_THAT_FAILED=$((SCENARIOS_THAT_FAILED + BASH_REMATCH[2])) + elif [[ ${SCENARIO_RESULTS} =~ [0-9]+[^0-9]+([0-9]+)[^0-9]+ ]] + then + # All failed, we got the second number. + # The text looked like "4 scenarios (4 failed)" + SCENARIOS_THAT_FAILED=$((SCENARIOS_THAT_FAILED + BASH_REMATCH[1])) + fi + fi + fi + + FAILED_SCENARIO_PATHS_COLORED=`awk '/Failed scenarios:/',0 ${TEST_LOG_FILE} | grep -a feature` + # There will be some ANSI escape codes for color in the FEATURE_COLORED var. + # Strip them out so we can pass just the ordinary feature details to Behat. + # Thanks to https://en.wikipedia.org/wiki/Tee_(command) and + # https://stackoverflow.com/questions/23416278/how-to-strip-ansi-escape-sequences-from-a-variable + # for ideas. + FAILED_SCENARIO_PATHS=$(echo "${FAILED_SCENARIO_PATHS_COLORED}" | sed "s/\x1b[^m]*m//g") + + # If something else went wrong, and there were no failed scenarios, + # then the awk, grep, sed command sequence above ends up with an empty string. + # Unset FAILED_SCENARIO_PATHS to avoid later code thinking that there might be + # one failed scenario. + if [ -z "${FAILED_SCENARIO_PATHS}" ] + then + unset FAILED_SCENARIO_PATHS + fi + + if [ -n "${EXPECTED_FAILURES_FILE}" ] + then + if [ -n "${BEHAT_SUITE_TO_RUN}" ] + then + echo "Checking expected failures for suite ${BEHAT_SUITE_TO_RUN}" + else + echo "Checking expected failures" + fi + + # Check that every failed scenario is in the list of expected failures + for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS} + do + SUITE_PATH=`dirname ${FAILED_SCENARIO_PATH}` + SUITE=`basename ${SUITE_PATH}` + SCENARIO=`basename ${FAILED_SCENARIO_PATH}` + SUITE_SCENARIO="${SUITE}/${SCENARIO}" + grep "\[${SUITE_SCENARIO}\]" "${EXPECTED_FAILURES_FILE}" > /dev/null + if [ $? -ne 0 ] + then + echo "Error: Scenario ${SUITE_SCENARIO} failed but was not expected to fail." + UNEXPECTED_FAILED_SCENARIOS+=("${SUITE_SCENARIO}") + fi + done + + # Check that every scenario in the list of expected failures did fail + while read SUITE_SCENARIO + do + # Ignore comment lines (starting with hash) + if [[ "${SUITE_SCENARIO}" =~ ^# ]] + then + continue + fi + # Match lines that have [someSuite/someName.feature:n] - the part inside the + # brackets is the suite, feature and line number of the expected failure. + # Else ignore the line. + if [[ "${SUITE_SCENARIO}" =~ \[([a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.feature:[0-9]+)] ]]; then + SUITE_SCENARIO="${BASH_REMATCH[1]}" + else + continue + fi + if [ -n "${BEHAT_SUITE_TO_RUN}" ] + then + # If the expected failure is not in the suite that is currently being run, + # then do not try and check that it failed. + REGEX_TO_MATCH="^${BEHAT_SUITE_TO_RUN}/" + if ! [[ "${SUITE_SCENARIO}" =~ ${REGEX_TO_MATCH} ]] + then + continue + fi + fi + + # look for the expected suite-scenario at the end of a line in the + # FAILED_SCENARIO_PATHS - for example looking for apiComments/comments.feature:9 + # we want to match lines like: + # tests/acceptance/features/apiComments/comments.feature:9 + # but not lines like:: + # tests/acceptance/features/apiComments/comments.feature:902 + echo "${FAILED_SCENARIO_PATHS}" | grep ${SUITE_SCENARIO}$ > /dev/null + if [ $? -ne 0 ] + then + echo "Info: Scenario ${SUITE_SCENARIO} was expected to fail but did not fail." + UNEXPECTED_PASSED_SCENARIOS+=("${SUITE_SCENARIO}") + fi + done < ${EXPECTED_FAILURES_FILE} + else + for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS} + do + SUITE_PATH=$(dirname "${FAILED_SCENARIO_PATH}") + SUITE=$(basename "${SUITE_PATH}") + SCENARIO=$(basename "${FAILED_SCENARIO_PATH}") + SUITE_SCENARIO="${SUITE}/${SCENARIO}" + UNEXPECTED_FAILED_SCENARIOS+=("${SUITE_SCENARIO}") + done + fi + + if [ ${BEHAT_EXIT_STATUS} -ne 0 ] && [ ${#FAILED_SCENARIO_PATHS[@]} -eq 0 ] + then + # Behat had some problem and there were no failed scenarios reported + # So the problem is something else. + # Possibly there were missing step definitions. Or Behat crashed badly, or... + UNEXPECTED_BEHAT_EXIT_STATUSES+=("${SUITE_FEATURE_TEXT} had behat exit status ${BEHAT_EXIT_STATUS}") + fi + + if [ "${BEHAT_TAGS_OPTION_FOUND}" != true ] + then + # The behat run specified to skip scenarios tagged @skip + # Report them in a dry-run so they can be seen + # Big red error output is displayed if there are no matching scenarios - send it to null + DRY_RUN_FILE=$(mktemp) + SKIP_TAGS="${TEST_TYPE_TAG}&&@skip" + ${BEHAT} --dry-run --colors -c ${BEHAT_YML} -f pretty ${BEHAT_SUITE_OPTION} --tags "${SKIP_TAGS}" ${BEHAT_FEATURE} 1>${DRY_RUN_FILE} 2>/dev/null + if grep -q -m 1 'No scenarios' "${DRY_RUN_FILE}" + then + # If there are no skip scenarios, then no need to report that + : + else + echo "" + echo "The following tests were skipped because they are tagged @skip:" + cat "${DRY_RUN_FILE}" | tee -a ${TEST_LOG_FILE} + fi + rm -f "${DRY_RUN_FILE}" + fi +} + +declare -x TEST_SERVER_URL + +if [ -z "${IPV4_URL}" ] +then + IPV4_URL="${TEST_SERVER_URL}" +fi + +if [ -z "${IPV6_URL}" ] +then + IPV6_URL="${TEST_SERVER_URL}" +fi + +# If a feature file has been specified but no suite, then deduce the suite +if [ -n "${BEHAT_FEATURE}" ] && [ -z "${BEHAT_SUITE}" ] +then + SUITE_PATH=`dirname ${BEHAT_FEATURE}` + BEHAT_SUITE=`basename ${SUITE_PATH}` +fi + +if [ -z "${BEHAT_YML}" ] +then + # Look for a behat.yml somewhere below the current working directory + # This saves app acceptance tests being forced to specify BEHAT_YML + BEHAT_YML="config/behat.yml" + if [ ! -f "${BEHAT_YML}" ] + then + BEHAT_YML="acceptance/config/behat.yml" + fi + if [ ! -f "${BEHAT_YML}" ] + then + BEHAT_YML="tests/acceptance/config/behat.yml" + fi + # If no luck above, then use the core behat.yml that should live below this script + if [ ! -f "${BEHAT_YML}" ] + then + BEHAT_YML="${SCRIPT_PATH}/config/behat.yml" + fi +fi + +BEHAT_CONFIG_DIR=$(dirname "${BEHAT_YML}") +ACCEPTANCE_DIR=$(dirname "${BEHAT_CONFIG_DIR}") +BEHAT_FEATURES_DIR="${ACCEPTANCE_DIR}/features" + +declare -a BEHAT_SUITES + +function get_behat_suites() { + # $1 type of suites to get "api" or "core-api" + # defaults to "api" + TYPE="$1" + if [[ -z "$TYPE" ]] + then + TYPE="api" + fi + ALL_SUITES=`find ${BEHAT_FEATURES_DIR}/ -type d -iname ${TYPE}* | sort | rev | cut -d"/" -f1 | rev` + COUNT_ALL_SUITES=`echo "${ALL_SUITES}" | wc -l` + #divide the suites letting it round down (could be zero) + MIN_SUITES_PER_RUN=$((${COUNT_ALL_SUITES} / ${DIVIDE_INTO_NUM_PARTS})) + #some jobs might need an extra suite + MAX_SUITES_PER_RUN=$((${MIN_SUITES_PER_RUN} + 1)) + # the remaining number of suites that need to be distributed (could be zero) + REMAINING_SUITES=$((${COUNT_ALL_SUITES} - (${DIVIDE_INTO_NUM_PARTS} * ${MIN_SUITES_PER_RUN}))) + + if [[ ${RUN_PART} -le ${REMAINING_SUITES} ]] + then + SUITES_THIS_RUN=${MAX_SUITES_PER_RUN} + SUITES_IN_PREVIOUS_RUNS=$((${MAX_SUITES_PER_RUN} * (${RUN_PART} - 1))) + else + SUITES_THIS_RUN=${MIN_SUITES_PER_RUN} + SUITES_IN_PREVIOUS_RUNS=$((((${MAX_SUITES_PER_RUN} * ${REMAINING_SUITES}) + (${MIN_SUITES_PER_RUN} * (${RUN_PART} - ${REMAINING_SUITES} - 1))))) + fi + + if [ ${SUITES_THIS_RUN} -eq 0 ] + then + echo "there are only ${COUNT_ALL_SUITES} suites, nothing to do in part ${RUN_PART}" + exit 0 + fi + + COUNT_FINISH_AND_TODO_SUITES=$((${SUITES_IN_PREVIOUS_RUNS} + ${SUITES_THIS_RUN})) + BEHAT_SUITES+=(`echo "${ALL_SUITES}" | head -n ${COUNT_FINISH_AND_TODO_SUITES} | tail -n ${SUITES_THIS_RUN}`) +} + +if [[ -n "${BEHAT_SUITE}" ]] +then + BEHAT_SUITES+=("${BEHAT_SUITE}") +else + if [[ -n "${RUN_PART}" ]]; then + if [[ "${ACCEPTANCE_TEST_TYPE}" == "core-api" ]]; then + get_behat_suites "core" + else + get_behat_suites "${ACCEPTANCE_TEST_TYPE}" + fi + fi +fi + +TEST_TYPE_TAG="@api" +TEST_TYPE_TEXT="API" + +# Always have "@api" +if [ -z "${BEHAT_FILTER_TAGS}" ] +then + BEHAT_FILTER_TAGS="${TEST_TYPE_TAG}" +else + # Be nice to the caller + # Remove any extra "&&" at the end of their tags list + BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS%&&}" + # Remove any extra "&&" at the beginning of their tags list + BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS#&&}" + BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS}&&${TEST_TYPE_TAG}" +fi + +# EMAIL_HOST defines where the system-under-test can find the email server (inbucket) +# for sending email. +if [ -z "${EMAIL_HOST}" ] +then + EMAIL_HOST="127.0.0.1" +fi + +# LOCAL_INBUCKET_HOST defines where this test script can find the Inbucket server +# for sending email. When testing a remote system, the Inbucket server somewhere +# "in the middle" might have a different host name from the point of view of +# the test script. +if [ -z "${LOCAL_EMAIL_HOST}" ] +then + LOCAL_EMAIL_HOST="${EMAIL_HOST}" +fi + +if [ -z "${EMAIL_SMTP_PORT}" ] +then + EMAIL_SMTP_PORT="2500" +fi + +# If the caller did not mention specific tags, skip the skipped tests by default +if [ "${BEHAT_TAGS_OPTION_FOUND}" = false ] +then + # If the caller has already specified specifically to run "@skip" scenarios + # then do not append "not @skip" + if [[ ! ${BEHAT_FILTER_TAGS} =~ "&&@skip&&" ]] + then + BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS}&&~@skip" + fi +fi + +export IPV4_URL +export IPV6_URL +export FILES_FOR_UPLOAD="${SCRIPT_PATH}/filesForUpload/" + +if [ "${TEST_OCIS}" != "true" ] && [ "${TEST_REVA}" != "true" ] +then + # We are testing on an ownCloud core server. + # Tell the tests to wait 1 second between each upload/delete action + # to avoid problems with actions that depend on timestamps in seconds. + export UPLOAD_DELETE_WAIT_TIME=1 +fi + +TEST_LOG_FILE=$(mktemp) +SCENARIOS_THAT_PASSED=0 +SCENARIOS_THAT_FAILED=0 + +if [ ${#BEHAT_SUITES[@]} -eq 0 ] && [ -z "${BEHAT_FEATURE}" ] +then + SUITE_FEATURE_TEXT="all ${TEST_TYPE_TEXT}" + run_behat_tests +else + if [ -n "${BEHAT_SUITE}" ] + then + SUITE_FEATURE_TEXT="${BEHAT_SUITE}" + fi + + if [ -n "${BEHAT_FEATURE}" ] + then + # If running a whole feature, it will be something like login.feature + # If running just a single scenario, it will also have the line number + # like login.feature:36 - which will be parsed correctly like a "file" + # by basename. + BEHAT_FEATURE_FILE=`basename ${BEHAT_FEATURE}` + SUITE_FEATURE_TEXT="${SUITE_FEATURE_TEXT} ${BEHAT_FEATURE_FILE}" + fi +fi + +for i in "${!BEHAT_SUITES[@]}" + do + BEHAT_SUITE_TO_RUN="${BEHAT_SUITES[$i]}" + BEHAT_SUITE_OPTION="--suite=${BEHAT_SUITE_TO_RUN}" + SUITE_FEATURE_TEXT="${BEHAT_SUITES[$i]}" + for rerun_number in $(seq 1 ${BEHAT_RERUN_TIMES}) + do + if ((${BEHAT_RERUN_TIMES} > 1)) + then + echo -e "\nTest repeat $rerun_number of ${BEHAT_RERUN_TIMES}" + fi + run_behat_tests + done +done + +TOTAL_SCENARIOS=$((SCENARIOS_THAT_PASSED + SCENARIOS_THAT_FAILED)) + +echo "runsh: Total ${TOTAL_SCENARIOS} scenarios (${SCENARIOS_THAT_PASSED} passed, ${SCENARIOS_THAT_FAILED} failed)" + +# 3 types of things can have gone wrong: +# - some scenario failed (and it was not expected to fail) +# - some scenario passed (but it was expected to fail) +# - Behat exited with non-zero status because of some other error +# If any of these happened then report about it and exit with status 1 (error) + +if [ ${#UNEXPECTED_FAILED_SCENARIOS[@]} -gt 0 ] +then + UNEXPECTED_FAILURE=true +else + UNEXPECTED_FAILURE=false +fi + +if [ ${#UNEXPECTED_PASSED_SCENARIOS[@]} -gt 0 ] +then + UNEXPECTED_SUCCESS=true +else + UNEXPECTED_SUCCESS=false +fi + +if [ ${#UNEXPECTED_BEHAT_EXIT_STATUSES[@]} -gt 0 ] +then + UNEXPECTED_BEHAT_EXIT_STATUS=true +else + UNEXPECTED_BEHAT_EXIT_STATUS=false +fi + +# If we got some unexpected success, and we only ran a single feature or scenario +# then the fact that some expected failures did not happen might be because those +# scenarios were never even run. +# Filter the UNEXPECTED_PASSED_SCENARIOS to remove scenarios that were not run. +if [ "${UNEXPECTED_SUCCESS}" = true ] +then + ACTUAL_UNEXPECTED_PASS=() + # if running a single feature or a single scenario + if [[ -n "${BEHAT_FEATURE}" ]] + then + for unexpected_passed_value in "${UNEXPECTED_PASSED_SCENARIOS[@]}" + do + # check only for the running feature + if [[ $BEHAT_FEATURE == *":"* ]] + then + BEHAT_FEATURE_WITH_LINE_NUM=$BEHAT_FEATURE + else + LINE_NUM=$(echo ${unexpected_passed_value} | cut -d":" -f2) + BEHAT_FEATURE_WITH_LINE_NUM=$BEHAT_FEATURE:$LINE_NUM + fi + if [[ $BEHAT_FEATURE_WITH_LINE_NUM == *"${unexpected_passed_value}" ]] + then + ACTUAL_UNEXPECTED_PASS+=("${unexpected_passed_value}") + fi + done + else + ACTUAL_UNEXPECTED_PASS=("${UNEXPECTED_PASSED_SCENARIOS[@]}") + fi + + if [ ${#ACTUAL_UNEXPECTED_PASS[@]} -eq 0 ] + then + UNEXPECTED_SUCCESS=false + fi +fi + +if [ "${UNEXPECTED_FAILURE}" = false ] && [ "${UNEXPECTED_SUCCESS}" = false ] && [ "${UNEXPECTED_BEHAT_EXIT_STATUS}" = false ] +then + FINAL_EXIT_STATUS=0 +else + FINAL_EXIT_STATUS=1 +fi + +if [ -n "${EXPECTED_FAILURES_FILE}" ] +then + echo "runsh: Exit code after checking expected failures: ${FINAL_EXIT_STATUS}" +fi + +if [ "${UNEXPECTED_FAILURE}" = true ] +then + tput setaf 3; echo "runsh: Total unexpected failed scenarios throughout the test run:" + tput setaf 1; printf "%s\n" "${UNEXPECTED_FAILED_SCENARIOS[@]}" +else + tput setaf 2; echo "runsh: There were no unexpected failures." +fi + +if [ "${UNEXPECTED_SUCCESS}" = true ] +then + tput setaf 3; echo "runsh: Total unexpected passed scenarios throughout the test run:" + tput setaf 1; printf "%s\n" "${ACTUAL_UNEXPECTED_PASS[@]}" +else + tput setaf 2; echo "runsh: There were no unexpected success." +fi + +if [ "${UNEXPECTED_BEHAT_EXIT_STATUS}" = true ] +then + tput setaf 3; echo "runsh: The following Behat test runs exited with non-zero status:" + tput setaf 1; printf "%s\n" "${UNEXPECTED_BEHAT_EXIT_STATUSES[@]}" +fi + +# sync the file-system so all output will be flushed to storage. +# In drone we sometimes see that the last lines of output are missing from the +# drone log. +sync + +# If we are running in drone CI, then sleep for a bit to (hopefully) let the +# drone agent send all the output to the drone server. +if [ -n "${CI_REPO}" ] +then + echo "sleeping for 30 seconds at end of test run" + sleep 30 +fi + +exit ${FINAL_EXIT_STATUS} diff --git a/tests/parallelDeployAcceptance/features/bootstrap/bootstrap.php b/tests/parallelDeployAcceptance/features/bootstrap/bootstrap.php index ec7e80734a1..4a3991c26ec 100644 --- a/tests/parallelDeployAcceptance/features/bootstrap/bootstrap.php +++ b/tests/parallelDeployAcceptance/features/bootstrap/bootstrap.php @@ -20,18 +20,14 @@ * */ -$pathToCore = \getenv('PATH_TO_CORE'); -if ($pathToCore === false) { - $pathToCore = "../core"; -} +use Composer\Autoload\ClassLoader; -require_once $pathToCore . '/tests/acceptance/features/bootstrap/bootstrap.php'; - -$classLoader = new \Composer\Autoload\ClassLoader(); +$classLoader = new ClassLoader(); $classLoader->addPsr4( "", - $pathToCore . "/tests/acceptance/features/bootstrap", + __DIR__ . "/../../../tests/acceptance/features/bootstrap", true ); +$classLoader->addPsr4("TestHelpers\\", __DIR__ . "/../../../TestHelpers", true); $classLoader->register(); diff --git a/vendor-bin/behat/composer.json b/vendor-bin/behat/composer.json index 999f00df863..56b0324d1e9 100644 --- a/vendor-bin/behat/composer.json +++ b/vendor-bin/behat/composer.json @@ -9,6 +9,7 @@ }, "require": { "behat/behat": "^3.9", + "behat/gherkin": "^4.9", "behat/mink": "1.7.1", "friends-of-behat/mink-extension": "^2.5", "ciaranmcnulty/behat-stepthroughextension" : "dev-master",