diff --git a/.drone.env b/.drone.env index 890b968f7f..23cb984d5e 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=e98e41f6b29ee7fb69e2cd1fe27c8a886804e7d5 +CORE_COMMITID=53a107c62f0e40bbfff0344ac3893ee40372a28e CORE_BRANCH=master diff --git a/.drone.star b/.drone.star index 4295ad7769..82ecc48f21 100644 --- a/.drone.star +++ b/.drone.star @@ -98,7 +98,6 @@ def main(ctx): return [ changelog(), checkStarlark(), - checkGoGenerate(), coverage(), buildAndPublishDocker(), buildOnly(), @@ -107,10 +106,7 @@ def main(ctx): litmusOcisOldWebdav(), litmusOcisNewWebdav(), litmusOcisSpacesDav(), - cs3ApiValidatorOcis(), - cs3ApiValidatorS3NG(), - # virtual views don't work on edge at the moment - #virtualViews(), + virtualViews(), ] + ocisIntegrationTests(6) + s3ngIntegrationTests(12) def buildAndPublishDocker(): @@ -301,7 +297,6 @@ def coverage(): "trigger": { "ref": [ "refs/heads/master", - "refs/heads/edge", "refs/pull/**", ], }, @@ -575,7 +570,7 @@ def virtualViews(): "PATH_TO_CORE": "/drone/src/tmp/testrunner", "TEST_SERVER_URL": "http://revad-services:20180", "OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/", - "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*", + "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/*", "STORAGE_DRIVER": "OCIS", "SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton", "TEST_REVA": "true", @@ -615,11 +610,8 @@ def litmusOcisOldWebdav(): "cd /drone/src/tests/oc-integration-tests/drone/", "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", + "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c shares.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -671,11 +663,8 @@ def litmusOcisNewWebdav(): "cd /drone/src/tests/oc-integration-tests/drone/", "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", + "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c shares.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -728,10 +717,8 @@ def litmusOcisSpacesDav(): "cd /drone/src/tests/oc-integration-tests/drone/", "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", + "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c shares.toml &", "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], @@ -754,7 +741,7 @@ def litmusOcisSpacesDav(): "commands": [ # The spaceid is randomly generated during the first login so we need this hack to construct the correct url. "curl -s -k -u einstein:relativity -I http://revad-services:20080/remote.php/dav/files/einstein", - "export LITMUS_URL=http://revad-services:20080/remote.php/dav/spaces/$(ls /drone/src/tmp/reva/data/spacetypes/personal/)", + "export LITMUS_URL=http://revad-services:20080/remote.php/dav/spaces/123e4567-e89b-12d3-a456-426655440000!$(ls /drone/src/tmp/reva/data/spaces/personal/)", "/usr/local/bin/litmus-wrapper", ], }, @@ -762,115 +749,6 @@ def litmusOcisSpacesDav(): "depends_on": ["changelog"], } -def cs3ApiValidatorOcis(): - return { - "kind": "pipeline", - "type": "docker", - "name": "cs3api-validator-ocis", - "platform": { - "os": "linux", - "arch": "amd64", - }, - "trigger": { - "event": { - "include": [ - "pull_request", - "tag", - ], - }, - }, - "steps": [ - makeStep("build-ci"), - { - "name": "revad-services", - "image": "registry.cern.ch/docker.io/library/golang:1.17", - "detach": True, - "commands": [ - "cd /drone/src/tests/oc-integration-tests/drone/", - "/drone/src/cmd/revad/revad -c frontend.toml &", - "/drone/src/cmd/revad/revad -c gateway.toml &", - "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c shares.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", - "/drone/src/cmd/revad/revad -c users.toml", - ], - }, - { - "name": "sleep-for-revad-start", - "image": "registry.cern.ch/docker.io/library/golang:1.17", - "commands": [ - "sleep 5", - ], - }, - { - "name": "cs3api-validator-ocis", - "image": "owncloud/cs3api-validator:latest", - "commands": [ - "/usr/bin/cs3api-validator /var/lib/cs3api-validator --endpoint=revad-services:19000", - ], - }, - ], - "depends_on": ["changelog"], - } - -def cs3ApiValidatorS3NG(): - return { - "kind": "pipeline", - "type": "docker", - "name": "cs3api-validator-S3NG", - "platform": { - "os": "linux", - "arch": "amd64", - }, - "trigger": { - "event": { - "include": [ - "pull_request", - "tag", - ], - }, - }, - "steps": [ - makeStep("build-ci"), - { - "name": "revad-services", - "image": "registry.cern.ch/docker.io/library/golang:1.17", - "detach": True, - "commands": [ - "cd /drone/src/tests/oc-integration-tests/drone/", - "/drone/src/cmd/revad/revad -c frontend.toml &", - "/drone/src/cmd/revad/revad -c gateway.toml &", - "/drone/src/cmd/revad/revad -c storage-users-s3ng.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c shares.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", - "/drone/src/cmd/revad/revad -c users.toml", - ], - }, - { - "name": "sleep-for-revad-start", - "image": "registry.cern.ch/docker.io/library/golang:1.17", - "commands": [ - "sleep 5", - ], - }, - { - "name": "cs3api-validator-S3NG", - "image": "owncloud/cs3api-validator:latest", - "commands": [ - "/usr/bin/cs3api-validator /var/lib/cs3api-validator --endpoint=revad-services:19000", - ], - }, - ], - "services": [ - cephService(), - ], - "depends_on": ["changelog"], - } - def ocisIntegrationTests(parallelRuns, skipExceptParts = []): pipelines = [] debugPartsEnabled = (len(skipExceptParts) != 0) @@ -906,11 +784,9 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", "/drone/src/cmd/revad/revad -c shares.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c machine-auth.toml &", + "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c ldap-users.toml", ], }, @@ -925,14 +801,14 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "environment": { "TEST_SERVER_URL": "http://revad-services:20080", "OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/", - "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*", + "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/*", "STORAGE_DRIVER": "OCIS", "SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton", "TEST_WITH_LDAP": "true", "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@issue-ocis-3023", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-OCIS-storage.md", @@ -983,12 +859,10 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []): "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", "/drone/src/cmd/revad/revad -c shares.toml &", + "/drone/src/cmd/revad/revad -c storage-home-s3ng.toml &", "/drone/src/cmd/revad/revad -c storage-users-s3ng.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", - "/drone/src/cmd/revad/revad -c storage-shares.toml &", - "/drone/src/cmd/revad/revad -c ldap-users.toml &", - "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", - "/drone/src/cmd/revad/revad -c machine-auth.toml", + "/drone/src/cmd/revad/revad -c ldap-users.toml", ], }, cloneOc10TestReposStep(), @@ -1002,14 +876,14 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []): "environment": { "TEST_SERVER_URL": "http://revad-services:20080", "OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/", - "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*", + "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/*", "STORAGE_DRIVER": "S3NG", "SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton", "TEST_WITH_LDAP": "true", "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@issue-ocis-3023", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-S3NG-storage.md", @@ -1060,26 +934,3 @@ def checkStarlark(): ], }, } - -def checkGoGenerate(): - return { - "kind": "pipeline", - "type": "docker", - "name": "check-go-generate", - "steps": [ - { - "name": "check-go-generate", - "image": "registry.cern.ch/docker.io/library/golang:1.17", - "commands": [ - "make go-generate", - "git diff --exit-code", - ], - }, - ], - "depends_on": [], - "trigger": { - "ref": [ - "refs/pull/**", - ], - }, - } diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0817a8b8..7f3e001e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,589 +1,37 @@ -Changelog for reva 2.2.0 (2022-04-12) +Changelog for reva 1.18.0 (2022-02-11) ======================================= -The following sections list the changes in reva 2.2.0 relevant to +The following sections list the changes in reva 1.18.0 relevant to reva users. The changes are ordered by importance. Summary ------- - * Fix #3373: Fix the permissions attribute in propfind responses - * Fix #2721: Fix locking and public link scope checker to make the WOPI server work - * Fix #2668: Minor cleanup - * Fix #2692: Ensure that the host in the ocs config endpoint has no protocol - * Fix #2709: Decomposed FS: return precondition failed if already locked - * Chg #2687: Allow link with no or edit permission - * Chg #2658: Small clean up of the ocdav code - * Enh #2691: Decomposed FS: return a reference to the parent - * Enh #2708: Rework LDAP configuration of user and group providers - * Enh #2665: Add embeddable ocdav go micro service - * Enh #2715: Introduced quicklinks - * Enh #3370: Enable all spaces members to list public shares - * Enh #3370: Enable space members to list shares inside the space - * Enh #2717: Add definitions for user and group events - -Details -------- - - * Bugfix #3373: Fix the permissions attribute in propfind responses - - Fixed the permissions that are returned when doing a propfind on a project space. - - https://github.com/owncloud/ocis/issues/3373 - https://github.com/cs3org/reva/pull/2713 - - * Bugfix #2721: Fix locking and public link scope checker to make the WOPI server work - - We've fixed the locking implementation to use the CS3api instead of the temporary opaque - values. We've fixed the scope checker on public links to allow the OpenInApp actions. - - These fixes have been done to use the cs3org/wopiserver with REVA edge. - - https://github.com/cs3org/reva/pull/2721 - - * Bugfix #2668: Minor cleanup - - - The `chunk_folder` config option is unused - Prevent a panic when looking up spaces - - https://github.com/cs3org/reva/pull/2668 - - * Bugfix #2692: Ensure that the host in the ocs config endpoint has no protocol - - We've fixed the host info in the ocs config endpoint so that it has no protocol, as ownCloud 10 - doesn't have it. - - https://github.com/cs3org/reva/pull/2692 - https://github.com/owncloud/ocis/pull/3113 - - * Bugfix #2709: Decomposed FS: return precondition failed if already locked - - We've fixed the return code from permission denied to precondition failed if a user tries to - lock an already locked file. - - https://github.com/cs3org/reva/pull/2709 - - * Change #2687: Allow link with no or edit permission - - Allow the creation of links with no permissions. These can be used to navigate to a file that a - user has access to. Allow setting edit permission on single file links (create and delete are - still blocked) Introduce endpoint to get information about a given token - - https://github.com/cs3org/reva/pull/2687 - - * Change #2658: Small clean up of the ocdav code - - Cleaned up the ocdav code to make it more readable and in one case a bit faster. - - https://github.com/cs3org/reva/pull/2658 - - * Enhancement #2691: Decomposed FS: return a reference to the parent - - We've implemented the changes from cs3org/cs3apis#167 in the DecomposedFS, so that a stat on a - resource always includes a reference to the parent of the resource. - - https://github.com/cs3org/reva/pull/2691 - - * Enhancement #2708: Rework LDAP configuration of user and group providers - - We reworked to LDAP configuration of the LDAP user and group provider to share a common - configuration scheme. Additionally the LDAP configuration no longer relies on templating - LDAP filters in the configuration which is error prone and can be confusing. Additionally the - providers are now somewhat more flexible about the group membership schema. Instead of only - supporting RFC2307 (posixGroup) style groups. It's now possible to also use standard LDAP - groups (groupOfName/groupOfUniqueNames) which track group membership by DN instead of - username (the behaviour is switched automatically depending on the group_objectclass - setting). - - The new LDAP configuration basically looks this: - - ```ini [grpc.services.userprovider.drivers.ldap] uri="ldaps://localhost:636" - insecure=true user_base_dn="ou=testusers,dc=owncloud,dc=com" - group_base_dn="ou=testgroups,dc=owncloud,dc=com" user_filter="" - user_objectclass="posixAccount" group_filter="" group_objectclass="posixGroup" - bind_username="cn=admin,dc=owncloud,dc=com" bind_password="admin" - idp="http://localhost:20080" - - [grpc.services.userprovider.drivers.ldap.user_schema] id="entryuuid" - displayName="displayName" userName="cn" - - [grpc.services.userprovider.drivers.ldap.group_schema] id="entryuuid" - displayName="cn" groupName="cn" member="memberUID" ``` - - `uri` defines the LDAP URI of the destination Server - - `insecure` allows to disable TLS Certifictate Validation (for development setups) - - `user_base_dn`/`group_base_dn` define the search bases for users and groups - - `user_filter`/`group_filter` allow to define additional LDAP filter of users and groups. - This could be e.g. `(objectclass=owncloud)` to match for an additional objectclass. - - `user_objectclass`/`group_objectclass` define the main objectclass of Users and Groups. - These are used to construct the LDAP filters - - `bind_username`/`bind_password` contain the authentication information for the LDAP - connections - - The `user_schema` and `group_schema` sections define the mapping from CS3 user/group - attributes to LDAP Attributes - - https://github.com/cs3org/reva/issues/2122 - https://github.com/cs3org/reva/issues/2124 - https://github.com/cs3org/reva/pull/2708 - - * Enhancement #2665: Add embeddable ocdav go micro service - - The new `pkg/micro/ocdav` package implements a go micro compatible version of the ocdav - service. - - https://github.com/cs3org/reva/pull/2665 - - * Enhancement #2715: Introduced quicklinks - - We now support Quicklinks. When creating a link with flag "quicklink=true", no new link will be - created when a link already exists. - - https://github.com/cs3org/reva/pull/2715 - - * Enhancement #3370: Enable all spaces members to list public shares - - Enhanced the json and cs3 public share manager so that it lists shares in spaces for all members. - - https://github.com/owncloud/ocis/issues/3370 - https://github.com/cs3org/reva/pull/2697 - - * Enhancement #3370: Enable space members to list shares inside the space - - If there are shared resources in a space then all members are allowed to see those shares. The - json share manager was enhanced to check if the user is allowed to see a share by checking the - grants on a resource. - - https://github.com/owncloud/ocis/issues/3370 - https://github.com/cs3org/reva/pull/2674 - https://github.com/cs3org/reva/pull/2710 - - * Enhancement #2717: Add definitions for user and group events - - Enhance the events package with definitions for user and group events. - - https://github.com/cs3org/reva/pull/2717 - https://github.com/cs3org/reva/pull/2724 - - -Changelog for reva 2.1.0 (2022-03-29) -======================================= - -The following sections list the changes in reva 2.1.0 relevant to -reva users. The changes are ordered by importance. - -Summary -------- - - * Fix #2636: Delay reconnect log for events - * Fix #2645: Avoid warning about missing .flock files - * Fix #2625: Fix locking on publik links and the decomposed filesystem - * Fix #2643: Emit linkaccessfailed event when share is nil - * Fix #2646: Replace public mountpoint fileid with grant fileid in ocdav - * Fix #2612: Adjust the scope handling to support the spaces architecture - * Fix #2621: Send events only if response code is `OK` - * Chg #2574: Switch NATS backend - * Chg #2667: Allow LDAP groups to have no gidNumber - * Chg #3233: Improve quota handling - * Chg #2600: Use the cs3 share api to manage spaces - * Enh #2644: Add new public share manager - * Enh #2626: Add new share manager - * Enh #2624: Add etags to virtual spaces - * Enh #2639: File Events - * Enh #2627: Add events for sharing action - * Enh #2664: Add grantID to mountpoint - * Enh #2622: Allow listing shares in spaces via the OCS API - * Enh #2623: Add space aliases - * Enh #2647: Add space specific events - * Enh #3345: Add the spaceid to propfind responses - * Enh #2616: Add etag to spaces response - * Enh #2628: Add spaces aware trash-bin API - -Details -------- - - * Bugfix #2636: Delay reconnect log for events - - Print reconnect information log only when reconnect time is bigger than a second - - https://github.com/cs3org/reva/pull/2636 - - * Bugfix #2645: Avoid warning about missing .flock files - - These flock files appear randomly because of file locking. We can savely ignore them. - - https://github.com/cs3org/reva/pull/2645 - - * Bugfix #2625: Fix locking on publik links and the decomposed filesystem - - We've fixed the behavior of locking on the decomposed filesystem, so that now app based locks - can be modified user independently (needed for WOPI integration). Also we added a check, if a - lock is already expired and if so, we lazily delete the lock. The InitiateUploadRequest now - adds the Lock to the upload metadata so that an upload to an locked file is possible. - - We'v added the locking api requests to the public link scope checks, so that locking also can be - used on public links (needed for WOPI integration). - - https://github.com/cs3org/reva/pull/2625 - - * Bugfix #2643: Emit linkaccessfailed event when share is nil - - The code no longer panics when a link access failed event has no share. - - https://github.com/cs3org/reva/pull/2643 - - * Bugfix #2646: Replace public mountpoint fileid with grant fileid in ocdav - - We now show the same resoucre id for resources when accessing them via a public links as when - using a logged in user. This allows the web ui to start a WOPI session with the correct resource - id. - - https://github.com/cs3org/reva/issues/2635 - https://github.com/cs3org/reva/pull/2646 - - * Bugfix #2612: Adjust the scope handling to support the spaces architecture - - The scope authentication interceptors weren't updated to the spaces architecture and - couldn't authenticate some requests. - - https://github.com/cs3org/reva/pull/2612 - - * Bugfix #2621: Send events only if response code is `OK` - - Before events middleware was sending events also when the resulting status code was not `OK`. - This is clearly a bug. - - https://github.com/cs3org/reva/pull/2621 - - * Change #2574: Switch NATS backend - - We've switched the NATS backend from Streaming to JetStream, since NATS Streaming is - depreciated. - - https://github.com/cs3org/reva/pull/2574 - - * Change #2667: Allow LDAP groups to have no gidNumber - - Similar to the user-provider allow a group to have no gidNumber. Assign a default in that case. - - https://github.com/cs3org/reva/pull/2667 - - * Change #3233: Improve quota handling - - GetQuota now returns 0 when no quota was set instead of the disk size. Also added a new return - value for the remaining space which will either be quota - used bytes or if no quota was set the - free disk size. - - https://github.com/owncloud/ocis/issues/3233 - https://github.com/cs3org/reva/pull/2666 - https://github.com/cs3org/reva/pull/2688 - - * Change #2600: Use the cs3 share api to manage spaces - - We now use the cs3 share Api to manage the space roles. We do not send the request to the share - manager, the permissions are stored in the storage provider - - https://github.com/cs3org/reva/pull/2600 - https://github.com/cs3org/reva/pull/2620 - https://github.com/cs3org/reva/pull/2687 - - * Enhancement #2644: Add new public share manager - - We added a new public share manager which uses the new metadata storage backend for persisting - the public share information. - - https://github.com/cs3org/reva/pull/2644 - - * Enhancement #2626: Add new share manager - - We added a new share manager which uses the new metadata storage backend for persisting the - share information. - - https://github.com/cs3org/reva/pull/2626 - - * Enhancement #2624: Add etags to virtual spaces - - The shares storage provider didn't include the etag in virtual spaces like the shares jail or - mountpoints. - - https://github.com/cs3org/reva/pull/2624 - - * Enhancement #2639: File Events - - Adds file based events. See `pkg/events/files.go` for full list - - https://github.com/cs3org/reva/pull/2639 - - * Enhancement #2627: Add events for sharing action - - Includes lifecycle events for shares and public links doesn't include federated sharing - events for now see full list of events in `pkg/events/types.go` - - https://github.com/cs3org/reva/pull/2627 - - * Enhancement #2664: Add grantID to mountpoint - - We distinguish between the mountpoint of a share and the grant where the original file is - located on the storage. - - https://github.com/cs3org/reva/pull/2664 - - * Enhancement #2622: Allow listing shares in spaces via the OCS API - - Added a `space_ref` parameter to the list shares endpoints so that one can list shares inside of - spaces. - - https://github.com/cs3org/reva/pull/2622 - - * Enhancement #2623: Add space aliases - - Space aliases can be used to resolve spaceIDs in a client. - - https://github.com/cs3org/reva/pull/2623 - - * Enhancement #2647: Add space specific events - - See `pkg/events/spaces.go` for full list - - https://github.com/cs3org/reva/pull/2647 - - * Enhancement #3345: Add the spaceid to propfind responses - - Added the spaceid to propfind responses so that clients have the necessary data to send - subsequent requests. - - https://github.com/owncloud/ocis/issues/3345 - https://github.com/cs3org/reva/pull/2657 - - * Enhancement #2616: Add etag to spaces response - - Added the spaces etag to the response when listing spaces. - - https://github.com/cs3org/reva/pull/2616 - - * Enhancement #2628: Add spaces aware trash-bin API - - Added the webdav trash-bin endpoint for spaces. - - https://github.com/cs3org/reva/pull/2628 - - -Changelog for reva 2.0.0 (2022-03-03) -======================================= - -The following sections list the changes in reva 2.0.0 relevant to -reva users. The changes are ordered by importance. - -Summary -------- - - * Fix #2457: Do not swallow error - * Fix #2422: Handle non existing spaces correctly - * Fix #2327: Enable changelog on edge branch * Fix #2370: Fixes for apps in public shares, project spaces for EOS driver - * Fix #2464: Pass spacegrants when adding member to space - * Fix #2430: Fix aggregated child folder id - * Fix #2348: Make archiver handle spaces protocol - * Fix #2452: Fix create space error message - * Fix #2445: Don't handle ids containing "/" in decomposedfs - * Fix #2285: Accept new userid idp format - * Fix #2503: Remove the protection from /v?.php/config endpoints - * Fix #2462: Public shares path needs to be set - * Fix #2427: Fix registry caching - * Fix #2298: Remove share refs from trashbin - * Fix #2433: Fix shares provider filter - * Fix #2351: Fix Statcache removing * Fix #2374: Fix webdav copy of zero byte files - * Fix #2336: Handle sending all permissions when creating public links - * Fix #2440: Add ArbitraryMetadataKeys to statcache key - * Fix #2582: Keep lock structs in a local map protected by a mutex - * Fix #2372: Make owncloudsql work with the spaces registry - * Fix #2416: The registry now returns complete space structs - * Fix #3066: Fix propfind listing for files - * Fix #2428: Remove unused home provider from config - * Fix #2334: Revert fix decomposedfs upload - * Fix #2415: Services should never return transport level errors - * Fix #2419: List project spaces for share recipients - * Fix #2501: Fix spaces stat - * Fix #2432: Use space reference when listing containers - * Fix #2572: Wait for nats server on middleware start - * Fix #2454: Fix webdav paths in PROPFINDS - * Chg #2329: Activate the statcache - * Chg #2596: Remove hash from public link urls - * Chg #2495: Remove the ownCloud storage driver - * Chg #2527: Store space attributes in decomposedFS - * Chg #2581: Update hard-coded status values - * Chg #2524: Use description during space creation - * Chg #2554: Shard nodes per space in decomposedfs - * Chg #2576: Harden xattrs errors - * Chg #2436: Replace template in GroupFilter for UserProvider with a simple string - * Chg #2429: Make archiver id based - * Chg #2340: Allow multiple space configurations per provider - * Chg #2396: The ocdav handler is now spaces aware - * Chg #2349: Require `ListRecycle` when listing trashbin - * Chg #2353: Reduce log output - * Chg #2542: Do not encode webDAV ids to base64 - * Chg #2519: Remove the auto creation of the .space folder - * Chg #2394: Remove logic from gateway - * Chg #2023: Add a sharestorageprovider - * Chg #2234: Add a spaces registry - * Chg #2339: Fix static registry regressions - * Chg #2370: Fix static registry regressions + * Fix #2478: Use ocs permission objects in the reva GRPC client + * Fix #2368: Return wrapped paths for recycled items in storage provider * Chg #2354: Return not found when updating non existent space - * Chg #2589: Remove deprecated linter modules - * Chg #2016: Move wrapping and unwrapping of paths to the storage gateway - * Enh #2591: Set up App Locks with basic locks * Enh #1209: Reva CephFS module v0.2.1 - * Enh #2511: Error handling cleanup in decomposed FS - * Enh #2516: Cleaned up some code - * Enh #2512: Consolidate xattr setter and getter * Enh #2341: Use CS3 permissions API - * Enh #2343: Allow multiple space type fileters on decomposedfs - * Enh #2460: Add locking support to decomposedfs - * Enh #2540: Refactored the xattrs package in the decomposedfs - * Enh #2463: Do not log whole nodes * Enh #2350: Add file locking methods to the storage and filesystem interfaces * Enh #2379: Add new file url of the app provider to the ocs capabilities * Enh #2369: Implement TouchFile from the CS3apis * Enh #2385: Allow to create new files with the app provider on public links * Enh #2397: Product field in OCS version * Enh #2393: Update tus/tusd to version 1.8.0 - * Enh #2522: Introduce events - * Enh #2528: Use an exclcusive write lock when writing multiple attributes - * Enh #2595: Add integration test for the groupprovider - * Enh #2439: Ignore handled errors when creating spaces - * Enh #2500: Invalidate listproviders cache - * Enh #2345: Don't assume that the LDAP groupid in reva matches the name - * Enh #2525: Allow using AD UUID as userId values - * Enh #2584: Allow running userprovider integration tests for the LDAP driver - * Enh #2585: Add metadata storage layer and indexer - * Enh #2163: Nextcloud-based share manager for pkg/ocm/share + * Enh #2205: Modify group and user managers to skip fetching specified metadata + * Enh #2232: Make ocs resource info cache interoperable across drivers + * Enh #2233: Populate owner data in the ocs and ocdav services * Enh #2278: OIDC driver changes for lightweight users - * Enh #2315: Add new attributes to public link propfinds - * Enh #2431: Delete shares when purging spaces - * Enh #2434: Refactor ocdav into smaller chunks - * Enh #2524: Add checks when removing space members - * Enh #2457: Restore spaces that were previously deleted - * Enh #2498: Include grants in list storage spaces response - * Enh #2344: Allow listing all storage spaces - * Enh #2547: Add an if-match check to the storage provider - * Enh #2486: Update cs3apis to include lock api changes - * Enh #2526: Upgrade ginkgo to v2 Details ------- - * Bugfix #2457: Do not swallow error - - Decomposedfs not longer swallows errors when creating a node fails. - - https://github.com/cs3org/reva/pull/2457 - - * Bugfix #2422: Handle non existing spaces correctly - - When looking up a space by id we returned the wrong status code. - - https://github.com/cs3org/reva/pull/2422 - - * Bugfix #2327: Enable changelog on edge branch - - We added a `branch` flag to the `tools/check-changelog/main.go` to fix changelog checks on - the edge branch. - - https://github.com/cs3org/reva/pull/2327 - * Bugfix #2370: Fixes for apps in public shares, project spaces for EOS driver https://github.com/cs3org/reva/pull/2370 - * Bugfix #2464: Pass spacegrants when adding member to space - - When creating a space grant there should not be created a new space. Unfortunately SpaceGrant - didn't work when adding members to a space. Now a value is placed in the ctx of the - storageprovider on which decomposedfs reacts - - https://github.com/cs3org/reva/pull/2464 - - * Bugfix #2430: Fix aggregated child folder id - - Propfind now returns the correct id and correctly aggregates the mtime and etag. - - https://github.com/cs3org/reva/pull/2430 - - * Bugfix #2348: Make archiver handle spaces protocol - - The archiver can now handle the spaces protocol - - https://github.com/cs3org/reva/pull/2348 - - * Bugfix #2452: Fix create space error message - - Create space no longer errors with list spaces error messages. - - https://github.com/cs3org/reva/pull/2452 - - * Bugfix #2445: Don't handle ids containing "/" in decomposedfs - - The storageprovider previously checked all ids without checking their validity this lead to - flaky test because it shouldn't check ids that are used from the public storage provider - - https://github.com/cs3org/reva/pull/2445 - - * Bugfix #2285: Accept new userid idp format - - The format for userid idp [changed](https://github.com/cs3org/cs3apis/pull/159) and - this broke [the ocmd - tutorial](https://reva.link/docs/tutorials/share-tutorial/#5-1-4-create-the-share) - This PR makes the provider authorizer interceptor accept both the old and the new string - format. - - https://github.com/cs3org/reva/issues/2285 - https://github.com/cs3org/reva/issues/2285 - See - and - - * Bugfix #2503: Remove the protection from /v?.php/config endpoints - - We've removed the protection from the "/v1.php/config" and "/v2.php/config" endpoints to be - API compatible with ownCloud 10. - - https://github.com/cs3org/reva/issues/2503 - https://github.com/owncloud/ocis/issues/1338 - - * Bugfix #2462: Public shares path needs to be set - - We need to set the relative path to the space root for public link shares to identify them in the - shares list. - - https://github.com/owncloud/ocis/issues/2462 - https://github.com/cs3org/reva/pull/2580 - - * Bugfix #2427: Fix registry caching - - We now cache space lookups per user. - - https://github.com/cs3org/reva/pull/2427 - - * Bugfix #2298: Remove share refs from trashbin - - https://github.com/cs3org/reva/pull/2298 - - * Bugfix #2433: Fix shares provider filter - - The shares storage provider now correctly filters space types - - https://github.com/cs3org/reva/pull/2433 - - * Bugfix #2351: Fix Statcache removing - - Removing from statcache didn't work correctly with different setups. Unified and fixed - - https://github.com/cs3org/reva/pull/2351 - * Bugfix #2374: Fix webdav copy of zero byte files We've fixed the webdav copy action of zero byte files, which was not performed because the @@ -594,258 +42,16 @@ Details https://github.com/cs3org/reva/pull/2374 https://github.com/cs3org/reva/pull/2309 - * Bugfix #2336: Handle sending all permissions when creating public links - - For backwards compatability we now reduce permissions to readonly when a create public link - carries all permissions. - - https://github.com/cs3org/reva/issues/2336 - https://github.com/owncloud/ocis/issues/1269 - - * Bugfix #2440: Add ArbitraryMetadataKeys to statcache key - - Otherwise stating with and without them would return the same result (because it is cached) - - https://github.com/cs3org/reva/pull/2440 - - * Bugfix #2582: Keep lock structs in a local map protected by a mutex - - Make sure that only one go routine or process can get the lock. - - https://github.com/cs3org/reva/pull/2582 - - * Bugfix #2372: Make owncloudsql work with the spaces registry - - Owncloudsql now works properly with the spaces registry. - - https://github.com/cs3org/reva/pull/2372 - - * Bugfix #2416: The registry now returns complete space structs - - We now return the complete space info, including name, path, owner, etc. instead of only path - and id. - - https://github.com/cs3org/reva/pull/2416 - - * Bugfix #3066: Fix propfind listing for files - - When doing a propfind for a file the result contained the files twice. - - https://github.com/owncloud/ocis/issues/3066 - https://github.com/cs3org/reva/pull/2506 - - * Bugfix #2428: Remove unused home provider from config - - The spaces registry does not use a home provider config. - - https://github.com/cs3org/reva/pull/2428 - - * Bugfix #2334: Revert fix decomposedfs upload - - Reverting https://github.com/cs3org/reva/pull/2330 to fix it properly - - https://github.com/cs3org/reva/pull/2334 - - * Bugfix #2415: Services should never return transport level errors - - The CS3 API adopted the grpc error codes from the [google grpc status - package](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto). - It also separates transport level errors from application level errors on purpose. This - allows sending CS3 messages over protocols other than GRPC. To keep that seperation, the - server side must always return `nil`, even though the code generation for go produces function - signatures for rpcs with an `error` return property. That allows clients to clearly - distinguish between transport level errors indicated by `err != nil` the error and - application level errors by checking the status code. - - https://github.com/cs3org/reva/pull/2415 - - * Bugfix #2419: List project spaces for share recipients - - The sharing handler now uses the ListProvider call on the registry when sharing by reference. - Furthermore, the decomposedfs now checks permissions on the root of a space so that a space is - listed for users that have access to a space. - - https://github.com/cs3org/reva/pull/2419 - - * Bugfix #2501: Fix spaces stat - - When stating a space e.g. the Share Jail and that space contains another space, in this case a - share then the stat would sometimes get the sub space instead of the Share Jail itself. - - https://github.com/cs3org/reva/pull/2501 - - * Bugfix #2432: Use space reference when listing containers - - The propfind handler now uses the reference for a space to make lookups relative. - - https://github.com/cs3org/reva/pull/2432 - - * Bugfix #2572: Wait for nats server on middleware start - - Use a retry mechanism to connect to the nats server when it is not ready yet - - https://github.com/cs3org/reva/pull/2572 - - * Bugfix #2454: Fix webdav paths in PROPFINDS - - The WebDAV Api was handling paths on spaces propfinds in the wrong way. This has been fixed in the - WebDAV layer. - - https://github.com/cs3org/reva/pull/2454 - - * Change #2329: Activate the statcache - - Activates the cache of stat request/responses in the gateway. - - https://github.com/cs3org/reva/pull/2329 - - * Change #2596: Remove hash from public link urls - - Public link urls do not contain the hash anymore, this is needed to support the ocis and web - history mode. - - https://github.com/cs3org/reva/pull/2596 - https://github.com/owncloud/ocis/pull/3109 - https://github.com/owncloud/web/pull/6363 - - * Change #2495: Remove the ownCloud storage driver - - We've removed the ownCloud storage driver because it was no longer maintained after the - ownCloud SQL storage driver was added. - - If you have been using the ownCloud storage driver, please switch to the ownCloud SQL storage - driver which brings you more features and is under active maintenance. - - https://github.com/cs3org/reva/pull/2495 - - * Change #2527: Store space attributes in decomposedFS - - We need to store more space attributes in the storage. This implements extended space - attributes in the decomposedFS - - https://github.com/cs3org/reva/pull/2527 - - * Change #2581: Update hard-coded status values - - The hard-coded version and product values have been updated to be consistent in all places in - the code. - - https://github.com/cs3org/reva/pull/2581 - - * Change #2524: Use description during space creation - - We can now use a space description during space creation. We also fixed a bug in the spaces roles. - Co-owners are now maintainers. - - https://github.com/cs3org/reva/pull/2524 - - * Change #2554: Shard nodes per space in decomposedfs - - The decomposedfs changas the on disk layout to shard nodes per space. - - https://github.com/cs3org/reva/pull/2554 - - * Change #2576: Harden xattrs errors - - Unwrap the error to get the root error. - - https://github.com/cs3org/reva/pull/2576 - - * Change #2436: Replace template in GroupFilter for UserProvider with a simple string - - Previously the "groupfilter" configuration for the UserProvider expected a go-template - value (based of of an `userpb.UserId` as it's input). And it assumed we could run a single LDAP - query to get all groups a user is member of by specifying the userid. However most LDAP Servers - store the GroupMembership by either username (e.g. in memberUID Attribute) or by the user's DN - (e.g. in member/uniqueMember). - - This change removes the userpb.UserId template processing from the groupfilter and replaces - it with a single string (the username) to cleanup the config a bit. Existing configs need to be - update to replace the go template references in `groupfilter` (e.g. `{{.}}` or - `{{.OpaqueId}}`) with `{{query}}`. - - https://github.com/cs3org/reva/pull/2436 - - * Change #2429: Make archiver id based - - The archiver now uses ids to walk the tree instead of paths - - https://github.com/cs3org/reva/pull/2429 - - * Change #2340: Allow multiple space configurations per provider - - The spaces registry can now use multiple space configurations to allow personal and project - spaces on the same provider - - https://github.com/cs3org/reva/pull/2340 - - * Change #2396: The ocdav handler is now spaces aware - - It will use LookupStorageSpaces and make only relative requests to the gateway. Temp comment - - https://github.com/cs3org/reva/pull/2396 - - * Change #2349: Require `ListRecycle` when listing trashbin - - Previously there was no check, so anyone could list anyones trash - - https://github.com/cs3org/reva/pull/2349 - - * Change #2353: Reduce log output - - Reduced log output. Some errors or warnings were logged multiple times or even unnecesarily. - - https://github.com/cs3org/reva/pull/2353 - - * Change #2542: Do not encode webDAV ids to base64 - - We removed the base64 encoding of the IDs and use the format ! with a `!` - delimiter. As a reserved delimiter it is URL safe. The IDs will be XML and JSON encoded as - necessary. - - https://github.com/cs3org/reva/pull/2542 - https://github.com/cs3org/reva/pull/2558 - - * Change #2519: Remove the auto creation of the .space folder - - We removed the auto creation of the .space folder because we don't develop this feature - further. - - https://github.com/cs3org/reva/pull/2519 - - * Change #2394: Remove logic from gateway - - The gateway will now hold no logic except forwarding the requests to other services. - - https://github.com/cs3org/reva/pull/2394 - - * Change #2023: Add a sharestorageprovider - - This PR adds a ShareStorageProvider which enables us to get rid of a lot of special casing in - other parts of the code. It also fixes several issues regarding shares and group shares. + * Bugfix #2478: Use ocs permission objects in the reva GRPC client - https://github.com/cs3org/reva/pull/2023 + There was a bug introduced by differing CS3APIs permission definitions for the same role + across services. This is a first step in making all services use consistent definitions. - * Change #2234: Add a spaces registry + https://github.com/cs3org/reva/pull/2478 - Spaces registry is supposed to manage spaces. Read - `pkg/storage/registry/spaces/Readme.md` for full details + * Bugfix #2368: Return wrapped paths for recycled items in storage provider - https://github.com/cs3org/reva/pull/2234 - - * Change #2339: Fix static registry regressions - - We fixed some smaller issues with using the static registry which were introduced with the - spaces registry changes. - - https://github.com/cs3org/reva/pull/2339 - - * Change #2370: Fix static registry regressions - - We fixed some smaller issues with using the static registry which were introduced with the - spaces registry changes. - - https://github.com/cs3org/reva/pull/2370 + https://github.com/cs3org/reva/pull/2368 * Change #2354: Return not found when updating non existent space @@ -853,48 +59,10 @@ Details https://github.com/cs3org/reva/pull/2354 - * Change #2589: Remove deprecated linter modules - - Replaced the deprecated linter modules with the recommended ones. - - https://github.com/cs3org/reva/pull/2589 - - * Change #2016: Move wrapping and unwrapping of paths to the storage gateway - - We've moved the wrapping and unwrapping of reference paths to the storage gateway so that the - storageprovider doesn't have to know its mount path. - - https://github.com/cs3org/reva/pull/2016 - - * Enhancement #2591: Set up App Locks with basic locks - - To set up App Locks basic locks are used now - - https://github.com/cs3org/reva/pull/2591 - * Enhancement #1209: Reva CephFS module v0.2.1 https://github.com/cs3org/reva/pull/1209 - * Enhancement #2511: Error handling cleanup in decomposed FS - - - Avoid inconsensitencies by cleaning up actions in case of err - - https://github.com/cs3org/reva/pull/2511 - - * Enhancement #2516: Cleaned up some code - - - Reduced type conversions []byte <-> string - pre-compile chunking regex - - https://github.com/cs3org/reva/pull/2516 - - * Enhancement #2512: Consolidate xattr setter and getter - - - Consolidate all metadata Get's and Set's to central functions. - Cleaner code by reduction of - casts - Easier to hook functionality like indexing - - https://github.com/cs3org/reva/pull/2512 - * Enhancement #2341: Use CS3 permissions API Added calls to the CS3 permissions API to the decomposedfs in order to check the user @@ -902,34 +70,6 @@ Details https://github.com/cs3org/reva/pull/2341 - * Enhancement #2343: Allow multiple space type fileters on decomposedfs - - The decomposedfs driver now evaluates multiple space type filters when listing storage - spaces. - - https://github.com/cs3org/reva/pull/2343 - - * Enhancement #2460: Add locking support to decomposedfs - - The decomposedfs now implements application level locking - - https://github.com/cs3org/reva/pull/2460 - - * Enhancement #2540: Refactored the xattrs package in the decomposedfs - - The xattrs package now uses the xattr.ENOATTR instead of os.ENODATA or os.ENOATTR to check - attribute existence. - - https://github.com/cs3org/reva/pull/2540 - https://github.com/cs3org/reva/pull/2541 - - * Enhancement #2463: Do not log whole nodes - - It turns out that logging whole node objects is very expensive and also spams the logs quite a - bit. Instead we just log the node ID now. - - https://github.com/cs3org/reva/pull/2463 - * Enhancement #2350: Add file locking methods to the storage and filesystem interfaces We've added the file locking methods from the CS3apis to the storage and filesystem @@ -976,146 +116,22 @@ Details https://github.com/cs3org/reva/issues/2393 https://github.com/cs3org/reva/pull/2224 - * Enhancement #2522: Introduce events - - This will introduce events into the system. Events are a simple way to bring information from - one service to another. Read `pkg/events/example` and subfolders for more information - - https://github.com/cs3org/reva/pull/2522 - - * Enhancement #2528: Use an exclcusive write lock when writing multiple attributes - - The xattr package can use an exclusive write lock when writing multiple extended attributes - - https://github.com/cs3org/reva/pull/2528 - - * Enhancement #2595: Add integration test for the groupprovider - - Some new integration tests were added to cover the groupprovider. - - https://github.com/cs3org/reva/pull/2595 - - * Enhancement #2439: Ignore handled errors when creating spaces - - The CreateStorageSpace no longer logs all error cases with error level logging - - https://github.com/cs3org/reva/pull/2439 + * Enhancement #2205: Modify group and user managers to skip fetching specified metadata - * Enhancement #2500: Invalidate listproviders cache + https://github.com/cs3org/reva/pull/2205 - We now invalidate the related listproviders cache entries when updating or deleting a storage - space. + * Enhancement #2232: Make ocs resource info cache interoperable across drivers - https://github.com/cs3org/reva/pull/2500 + https://github.com/cs3org/reva/pull/2232 - * Enhancement #2345: Don't assume that the LDAP groupid in reva matches the name + * Enhancement #2233: Populate owner data in the ocs and ocdav services - This allows using attributes like e.g. `entryUUID` or any custom id attribute as the id for - groups. - - https://github.com/cs3org/reva/pull/2345 - - * Enhancement #2525: Allow using AD UUID as userId values - - Active Directory UUID attributes (like e.g. objectGUID) use the LDAP octectString Syntax. In - order to be able to use them as userids in reva, they need to be converted to their string - representation. - - https://github.com/cs3org/reva/pull/2525 - - * Enhancement #2584: Allow running userprovider integration tests for the LDAP driver - - We extended the integration test suite for the userprovider to allow running it with an LDAP - server. - - https://github.com/cs3org/reva/pull/2584 - - * Enhancement #2585: Add metadata storage layer and indexer - - We ported over and enhanced the metadata storage layer and indexer from ocis-pkg so that it can - be used by reva services as well. - - https://github.com/cs3org/reva/pull/2585 - - * Enhancement #2163: Nextcloud-based share manager for pkg/ocm/share - - Note that pkg/ocm/share is very similar to pkg/share, but it deals with cs3/sharing/ocm - whereas pkg/share deals with cs3/sharing/collaboration - - https://github.com/cs3org/reva/pull/2163 + https://github.com/cs3org/reva/pull/2233 * Enhancement #2278: OIDC driver changes for lightweight users https://github.com/cs3org/reva/pull/2278 - * Enhancement #2315: Add new attributes to public link propfinds - - Added a new property "oc:signature-auth" to public link propfinds. This is a necessary change - to be able to support archive downloads in password protected public links. - - https://github.com/cs3org/reva/pull/2315 - - * Enhancement #2431: Delete shares when purging spaces - - Implemented the second step of the two step spaces delete process. The first step is marking the - space as deleted, the second step is actually purging the space. During the second step all - shares, including public shares, in the space will be deleted. When deleting a space the blobs - are currently not yet deleted since the decomposedfs will receive some changes soon. - - https://github.com/cs3org/reva/pull/2431 - https://github.com/cs3org/reva/pull/2458 - - * Enhancement #2434: Refactor ocdav into smaller chunks - - That increases code clarity and enables testing. - - https://github.com/cs3org/reva/pull/2434 - - * Enhancement #2524: Add checks when removing space members - - - Removed owners from project spaces - Prevent deletion of last space manager - Viewers and - editors can always be deleted - Managers can only be deleted when there will be at least one - remaining - - https://github.com/cs3org/reva/pull/2524 - - * Enhancement #2457: Restore spaces that were previously deleted - - After the first step of the two step delete process an admin can decide to restore the space - instead of deleting it. This will undo the deletion and all files and shares are accessible - again - - https://github.com/cs3org/reva/pull/2457 - - * Enhancement #2498: Include grants in list storage spaces response - - Added the grants to the response of list storage spaces. This allows service clients to show who - has access to a space. - - https://github.com/cs3org/reva/pull/2498 - - * Enhancement #2344: Allow listing all storage spaces - - To implement the drives api we now list all spaces when no filter is given. The Provider info will - not contain any spaces as the client is responsible for looking up the spaces. - - https://github.com/cs3org/reva/pull/2344 - - * Enhancement #2547: Add an if-match check to the storage provider - - Implement a check for the if-match value in InitiateFileUpload to prevent overwrites of newer - versions. - - https://github.com/cs3org/reva/pull/2547 - - * Enhancement #2486: Update cs3apis to include lock api changes - - https://github.com/cs3org/reva/pull/2486 - - * Enhancement #2526: Upgrade ginkgo to v2 - - https://github.com/cs3org/reva/pull/2526 - Changelog for reva 1.17.0 (2021-12-09) ======================================= diff --git a/Dockerfile.revad-ceph b/Dockerfile.revad-ceph index 186acfcc63..a547940fe2 100644 --- a/Dockerfile.revad-ceph +++ b/Dockerfile.revad-ceph @@ -16,7 +16,7 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -FROM ceph/daemon-base +FROM quay.ceph.io/ceph-ci/ceph:pacific RUN dnf update -y && dnf install -y \ git \ diff --git a/Makefile b/Makefile index e7b4318eac..5e8ab253c3 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ test-go-version: # for manual building only deps: cd /tmp && rm -rf golangci-lint && git clone --quiet -b 'v1.42.1' --single-branch --depth 1 https://github.com/golangci/golangci-lint &> /dev/null && cd golangci-lint/cmd/golangci-lint && go install - cd /tmp && go get golang.org/x/tools/cmd/goimports + cd /tmp && go install golang.org/x/tools/cmd/goimports@latest build-ci: off go build -ldflags ${CI_BUILD_FLAGS} -o ./cmd/revad/revad ./cmd/revad diff --git a/README.md b/README.md index d1c10bd005..3e8521308f 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,18 @@ This will require some PHP-related tools to run, for instance on Ubuntu you will On every commit on the master branch (including merged Pull Requests) a new release will be created and available at [daily releases](https://reva-releases.web.cern.ch/reva-releases). +## Major versions + +There are currently two major versions in active development. + +### 1.x versions + +The ``master`` branch is the stable development branch. Releases from master are tagged as 1.x.x versions following [semver](https://semver.org/). + +### 2.x versions + +The ``edge`` branch is used to develop the next version of this project. The edge branch is based on a new concept named "Spaces" and a new set of the CS3APIs are being implemented, making it **not compatible** with `master` branch. Releases from `edge` are tagged as *2.x.x* versions following [semver](https://semver.org/). + ## Run it using Docker See [https://hub.docker.com/r/cs3org/reva](https://hub.docker.com/r/cs3org/reva). diff --git a/RELEASE_DATE b/RELEASE_DATE index 44966a151f..0819a81c44 100644 --- a/RELEASE_DATE +++ b/RELEASE_DATE @@ -1 +1 @@ -2022-04-12 \ No newline at end of file +2022-02-11 \ No newline at end of file diff --git a/VERSION b/VERSION index e3a4f19336..744068368f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 \ No newline at end of file +1.18.0 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/cephfs-driver.md b/changelog/1.18.0_2022-02-11/cephfs-driver.md new file mode 100644 index 0000000000..9015c61538 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/cephfs-driver.md @@ -0,0 +1,3 @@ +Enhancement: Reva CephFS module v0.2.1 + +https://github.com/cs3org/reva/pull/1209 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/cs3-permissions-service.md b/changelog/1.18.0_2022-02-11/cs3-permissions-service.md new file mode 100644 index 0000000000..b792173e20 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/cs3-permissions-service.md @@ -0,0 +1,5 @@ +Enhancement: Use CS3 permissions API + +Added calls to the CS3 permissions API to the decomposedfs in order to check the user permissions. + +https://github.com/cs3org/reva/pull/2341 diff --git a/changelog/1.18.0_2022-02-11/enhancement-add-locking-methods-to-interfaces.md b/changelog/1.18.0_2022-02-11/enhancement-add-locking-methods-to-interfaces.md new file mode 100644 index 0000000000..edf8e4b902 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-add-locking-methods-to-interfaces.md @@ -0,0 +1,8 @@ +Enhancement: add file locking methods to the storage and filesystem interfaces + +We've added the file locking methods from the CS3apis to the storage and filesystem +interfaces. As of now they are dummy implementations and will only return "unimplemented" +errors. + +https://github.com/cs3org/reva/pull/2350 +https://github.com/cs3org/cs3apis/pull/160 diff --git a/changelog/1.18.0_2022-02-11/enhancement-add-new-file-capability.md b/changelog/1.18.0_2022-02-11/enhancement-add-new-file-capability.md new file mode 100644 index 0000000000..23805aeff3 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-add-new-file-capability.md @@ -0,0 +1,8 @@ +Enhancement: add new file url of the app provider to the ocs capabilities + +We've added the new file capability of the app provider to the ocs capabilities, so that +clients can discover this url analogous to the app list and file open urls. + +https://github.com/cs3org/reva/pull/2379 +https://github.com/owncloud/ocis/pull/2884 +https://github.com/owncloud/web/pull/5890#issuecomment-993905242 diff --git a/changelog/1.18.0_2022-02-11/enhancement-add-touch-file.md b/changelog/1.18.0_2022-02-11/enhancement-add-touch-file.md new file mode 100644 index 0000000000..8e9c04e0b9 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-add-touch-file.md @@ -0,0 +1,6 @@ +Enhancement: Implement TouchFile from the CS3apis + +We've updated the CS3apis and implemented the TouchFile method. + +https://github.com/cs3org/reva/pull/2369 +https://github.com/cs3org/cs3apis/pull/154 diff --git a/changelog/1.18.0_2022-02-11/enhancement-app-new-public-link.md b/changelog/1.18.0_2022-02-11/enhancement-app-new-public-link.md new file mode 100644 index 0000000000..01e315441b --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-app-new-public-link.md @@ -0,0 +1,5 @@ +Enhancement: Allow to create new files with the app provider on public links + +We've added the option to create files with the app provider on public links. + +https://github.com/cs3org/reva/pull/2385 diff --git a/changelog/1.18.0_2022-02-11/enhancement-product-field-in-ocs-version.md b/changelog/1.18.0_2022-02-11/enhancement-product-field-in-ocs-version.md new file mode 100644 index 0000000000..a897200697 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-product-field-in-ocs-version.md @@ -0,0 +1,5 @@ +Enhancement: Product field in OCS version + +We've added a new field to the OCS Version, which is supposed to announce the product name. The web ui as a client will make use of it to make the backend product and version available (e.g. for easier bug reports). + +https://github.com/cs3org/reva/pull/2397 diff --git a/changelog/1.18.0_2022-02-11/enhancement-update-tusd.md b/changelog/1.18.0_2022-02-11/enhancement-update-tusd.md new file mode 100644 index 0000000000..d4b6deabdd --- /dev/null +++ b/changelog/1.18.0_2022-02-11/enhancement-update-tusd.md @@ -0,0 +1,6 @@ +Enhancement: update tus/tusd to version 1.8.0 + +We've update tus/tusd to version 1.8.0. + +https://github.com/cs3org/reva/issues/2393 +https://github.com/cs3org/reva/pull/2224 diff --git a/changelog/1.18.0_2022-02-11/eos-fixes.md b/changelog/1.18.0_2022-02-11/eos-fixes.md new file mode 100644 index 0000000000..e0d0cbac7a --- /dev/null +++ b/changelog/1.18.0_2022-02-11/eos-fixes.md @@ -0,0 +1,3 @@ +Bugfix: Fixes for apps in public shares, project spaces for EOS driver + +https://github.com/cs3org/reva/pull/2370 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/fetch-groups-members-config.md b/changelog/1.18.0_2022-02-11/fetch-groups-members-config.md new file mode 100644 index 0000000000..6f375857c9 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/fetch-groups-members-config.md @@ -0,0 +1,3 @@ +Enhancement: Modify group and user managers to skip fetching specified metadata + +https://github.com/cs3org/reva/pull/2205 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/fix-webdav-copy-zero-byte-file.md b/changelog/1.18.0_2022-02-11/fix-webdav-copy-zero-byte-file.md new file mode 100644 index 0000000000..2fc971cd54 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/fix-webdav-copy-zero-byte-file.md @@ -0,0 +1,9 @@ +Bugfix: Fix webdav copy of zero byte files + +We've fixed the webdav copy action of zero byte files, which was not performed +because the webdav api assumed, that zero byte uploads are created when initiating +the upload, which was recently removed from all storage drivers. Therefore the +webdav api also uploads zero byte files after initiating the upload. + +https://github.com/cs3org/reva/pull/2374 +https://github.com/cs3org/reva/pull/2309 diff --git a/changelog/1.18.0_2022-02-11/ocs-cache-drivers.md b/changelog/1.18.0_2022-02-11/ocs-cache-drivers.md new file mode 100644 index 0000000000..02df2267e9 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/ocs-cache-drivers.md @@ -0,0 +1,3 @@ +Enhancement: Make ocs resource info cache interoperable across drivers + +https://github.com/cs3org/reva/pull/2232 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/ocs-user-data.md b/changelog/1.18.0_2022-02-11/ocs-user-data.md new file mode 100644 index 0000000000..7892d4c4ea --- /dev/null +++ b/changelog/1.18.0_2022-02-11/ocs-user-data.md @@ -0,0 +1,3 @@ +Enhancement: Populate owner data in the ocs and ocdav services + +https://github.com/cs3org/reva/pull/2233 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/oidc-lw-users.md b/changelog/1.18.0_2022-02-11/oidc-lw-users.md new file mode 100644 index 0000000000..2053f69551 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/oidc-lw-users.md @@ -0,0 +1,3 @@ +Enhancement: OIDC driver changes for lightweight users + +https://github.com/cs3org/reva/pull/2278 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/share-create-perm-fix.md b/changelog/1.18.0_2022-02-11/share-create-perm-fix.md new file mode 100644 index 0000000000..5dda4c3234 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/share-create-perm-fix.md @@ -0,0 +1,7 @@ +Bugfix: Use ocs permission objects in the reva GRPC client + +There was a bug introduced by differing CS3APIs permission definitions +for the same role across services. This is a first step in making +all services use consistent definitions. + +https://github.com/cs3org/reva/pull/2478 \ No newline at end of file diff --git a/changelog/1.18.0_2022-02-11/update-handle-empty-spaces.md b/changelog/1.18.0_2022-02-11/update-handle-empty-spaces.md new file mode 100644 index 0000000000..6ee2ebe634 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/update-handle-empty-spaces.md @@ -0,0 +1,5 @@ +Change: Return not found when updating non existent space + +If a spaceid of a space which is updated doesn't exist, handle it as a not found error. + +https://github.com/cs3org/reva/pull/2354 diff --git a/changelog/1.18.0_2022-02-11/wrap-recycle-paths.md b/changelog/1.18.0_2022-02-11/wrap-recycle-paths.md new file mode 100644 index 0000000000..6f818149f8 --- /dev/null +++ b/changelog/1.18.0_2022-02-11/wrap-recycle-paths.md @@ -0,0 +1,3 @@ +Bugfix: Return wrapped paths for recycled items in storage provider + +https://github.com/cs3org/reva/pull/2368 \ No newline at end of file diff --git a/changelog/NOTE.md b/changelog/NOTE.md index 5c275010ff..e13975e7f2 100644 --- a/changelog/NOTE.md +++ b/changelog/NOTE.md @@ -1,172 +1,135 @@ -Changelog for reva 2.2.0 (2022-04-12) +Changelog for reva 1.18.0 (2022-02-11) ======================================= -The following sections list the changes in reva 2.2.0 relevant to +The following sections list the changes in reva 1.18.0 relevant to reva users. The changes are ordered by importance. Summary ------- - * Fix #3373: Fix the permissions attribute in propfind responses - * Fix #2721: Fix locking and public link scope checker to make the WOPI server work - * Fix #2668: Minor cleanup - * Fix #2692: Ensure that the host in the ocs config endpoint has no protocol - * Fix #2709: Decomposed FS: return precondition failed if already locked - * Chg #2687: Allow link with no or edit permission - * Chg #2658: Small clean up of the ocdav code - * Enh #2691: Decomposed FS: return a reference to the parent - * Enh #2708: Rework LDAP configuration of user and group providers - * Enh #2665: Add embeddable ocdav go micro service - * Enh #2715: Introduced quicklinks - * Enh #3370: Enable all spaces members to list public shares - * Enh #3370: Enable space members to list shares inside the space - * Enh #2717: Add definitions for user and group events + * Fix #2370: Fixes for apps in public shares, project spaces for EOS driver + * Fix #2374: Fix webdav copy of zero byte files + * Fix #2478: Use ocs permission objects in the reva GRPC client + * Fix #2368: Return wrapped paths for recycled items in storage provider + * Chg #2354: Return not found when updating non existent space + * Enh #1209: Reva CephFS module v0.2.1 + * Enh #2341: Use CS3 permissions API + * Enh #2350: Add file locking methods to the storage and filesystem interfaces + * Enh #2379: Add new file url of the app provider to the ocs capabilities + * Enh #2369: Implement TouchFile from the CS3apis + * Enh #2385: Allow to create new files with the app provider on public links + * Enh #2397: Product field in OCS version + * Enh #2393: Update tus/tusd to version 1.8.0 + * Enh #2205: Modify group and user managers to skip fetching specified metadata + * Enh #2232: Make ocs resource info cache interoperable across drivers + * Enh #2233: Populate owner data in the ocs and ocdav services + * Enh #2278: OIDC driver changes for lightweight users Details ------- - * Bugfix #3373: Fix the permissions attribute in propfind responses + * Bugfix #2370: Fixes for apps in public shares, project spaces for EOS driver - Fixed the permissions that are returned when doing a propfind on a project space. + https://github.com/cs3org/reva/pull/2370 - https://github.com/owncloud/ocis/issues/3373 - https://github.com/cs3org/reva/pull/2713 + * Bugfix #2374: Fix webdav copy of zero byte files - * Bugfix #2721: Fix locking and public link scope checker to make the WOPI server work + We've fixed the webdav copy action of zero byte files, which was not performed because the + webdav api assumed, that zero byte uploads are created when initiating the upload, which was + recently removed from all storage drivers. Therefore the webdav api also uploads zero byte + files after initiating the upload. - We've fixed the locking implementation to use the CS3api instead of the temporary opaque - values. We've fixed the scope checker on public links to allow the OpenInApp actions. + https://github.com/cs3org/reva/pull/2374 + https://github.com/cs3org/reva/pull/2309 - These fixes have been done to use the cs3org/wopiserver with REVA edge. + * Bugfix #2478: Use ocs permission objects in the reva GRPC client - https://github.com/cs3org/reva/pull/2721 + There was a bug introduced by differing CS3APIs permission definitions for the same role + across services. This is a first step in making all services use consistent definitions. - * Bugfix #2668: Minor cleanup + https://github.com/cs3org/reva/pull/2478 - - The `chunk_folder` config option is unused - Prevent a panic when looking up spaces + * Bugfix #2368: Return wrapped paths for recycled items in storage provider - https://github.com/cs3org/reva/pull/2668 + https://github.com/cs3org/reva/pull/2368 - * Bugfix #2692: Ensure that the host in the ocs config endpoint has no protocol + * Change #2354: Return not found when updating non existent space - We've fixed the host info in the ocs config endpoint so that it has no protocol, as ownCloud 10 - doesn't have it. + If a spaceid of a space which is updated doesn't exist, handle it as a not found error. - https://github.com/cs3org/reva/pull/2692 - https://github.com/owncloud/ocis/pull/3113 + https://github.com/cs3org/reva/pull/2354 - * Bugfix #2709: Decomposed FS: return precondition failed if already locked + * Enhancement #1209: Reva CephFS module v0.2.1 - We've fixed the return code from permission denied to precondition failed if a user tries to - lock an already locked file. + https://github.com/cs3org/reva/pull/1209 - https://github.com/cs3org/reva/pull/2709 + * Enhancement #2341: Use CS3 permissions API - * Change #2687: Allow link with no or edit permission + Added calls to the CS3 permissions API to the decomposedfs in order to check the user + permissions. - Allow the creation of links with no permissions. These can be used to navigate to a file that a - user has access to. Allow setting edit permission on single file links (create and delete are - still blocked) Introduce endpoint to get information about a given token + https://github.com/cs3org/reva/pull/2341 - https://github.com/cs3org/reva/pull/2687 + * Enhancement #2350: Add file locking methods to the storage and filesystem interfaces - * Change #2658: Small clean up of the ocdav code + We've added the file locking methods from the CS3apis to the storage and filesystem + interfaces. As of now they are dummy implementations and will only return "unimplemented" + errors. - Cleaned up the ocdav code to make it more readable and in one case a bit faster. + https://github.com/cs3org/reva/pull/2350 + https://github.com/cs3org/cs3apis/pull/160 - https://github.com/cs3org/reva/pull/2658 + * Enhancement #2379: Add new file url of the app provider to the ocs capabilities - * Enhancement #2691: Decomposed FS: return a reference to the parent + We've added the new file capability of the app provider to the ocs capabilities, so that clients + can discover this url analogous to the app list and file open urls. - We've implemented the changes from cs3org/cs3apis#167 in the DecomposedFS, so that a stat on a - resource always includes a reference to the parent of the resource. + https://github.com/cs3org/reva/pull/2379 + https://github.com/owncloud/ocis/pull/2884 + https://github.com/owncloud/web/pull/5890#issuecomment-993905242 - https://github.com/cs3org/reva/pull/2691 + * Enhancement #2369: Implement TouchFile from the CS3apis - * Enhancement #2708: Rework LDAP configuration of user and group providers + We've updated the CS3apis and implemented the TouchFile method. - We reworked to LDAP configuration of the LDAP user and group provider to share a common - configuration scheme. Additionally the LDAP configuration no longer relies on templating - LDAP filters in the configuration which is error prone and can be confusing. Additionally the - providers are now somewhat more flexible about the group membership schema. Instead of only - supporting RFC2307 (posixGroup) style groups. It's now possible to also use standard LDAP - groups (groupOfName/groupOfUniqueNames) which track group membership by DN instead of - username (the behaviour is switched automatically depending on the group_objectclass - setting). + https://github.com/cs3org/reva/pull/2369 + https://github.com/cs3org/cs3apis/pull/154 - The new LDAP configuration basically looks this: + * Enhancement #2385: Allow to create new files with the app provider on public links - ```ini [grpc.services.userprovider.drivers.ldap] uri="ldaps://localhost:636" - insecure=true user_base_dn="ou=testusers,dc=owncloud,dc=com" - group_base_dn="ou=testgroups,dc=owncloud,dc=com" user_filter="" - user_objectclass="posixAccount" group_filter="" group_objectclass="posixGroup" - bind_username="cn=admin,dc=owncloud,dc=com" bind_password="admin" - idp="http://localhost:20080" + We've added the option to create files with the app provider on public links. - [grpc.services.userprovider.drivers.ldap.user_schema] id="entryuuid" - displayName="displayName" userName="cn" + https://github.com/cs3org/reva/pull/2385 - [grpc.services.userprovider.drivers.ldap.group_schema] id="entryuuid" - displayName="cn" groupName="cn" member="memberUID" ``` + * Enhancement #2397: Product field in OCS version - `uri` defines the LDAP URI of the destination Server + We've added a new field to the OCS Version, which is supposed to announce the product name. The + web ui as a client will make use of it to make the backend product and version available (e.g. for + easier bug reports). - `insecure` allows to disable TLS Certifictate Validation (for development setups) + https://github.com/cs3org/reva/pull/2397 - `user_base_dn`/`group_base_dn` define the search bases for users and groups + * Enhancement #2393: Update tus/tusd to version 1.8.0 - `user_filter`/`group_filter` allow to define additional LDAP filter of users and groups. - This could be e.g. `(objectclass=owncloud)` to match for an additional objectclass. + We've update tus/tusd to version 1.8.0. - `user_objectclass`/`group_objectclass` define the main objectclass of Users and Groups. - These are used to construct the LDAP filters + https://github.com/cs3org/reva/issues/2393 + https://github.com/cs3org/reva/pull/2224 - `bind_username`/`bind_password` contain the authentication information for the LDAP - connections + * Enhancement #2205: Modify group and user managers to skip fetching specified metadata - The `user_schema` and `group_schema` sections define the mapping from CS3 user/group - attributes to LDAP Attributes + https://github.com/cs3org/reva/pull/2205 - https://github.com/cs3org/reva/issues/2122 - https://github.com/cs3org/reva/issues/2124 - https://github.com/cs3org/reva/pull/2708 + * Enhancement #2232: Make ocs resource info cache interoperable across drivers - * Enhancement #2665: Add embeddable ocdav go micro service + https://github.com/cs3org/reva/pull/2232 - The new `pkg/micro/ocdav` package implements a go micro compatible version of the ocdav - service. + * Enhancement #2233: Populate owner data in the ocs and ocdav services - https://github.com/cs3org/reva/pull/2665 + https://github.com/cs3org/reva/pull/2233 - * Enhancement #2715: Introduced quicklinks + * Enhancement #2278: OIDC driver changes for lightweight users - We now support Quicklinks. When creating a link with flag "quicklink=true", no new link will be - created when a link already exists. - - https://github.com/cs3org/reva/pull/2715 - - * Enhancement #3370: Enable all spaces members to list public shares - - Enhanced the json and cs3 public share manager so that it lists shares in spaces for all members. - - https://github.com/owncloud/ocis/issues/3370 - https://github.com/cs3org/reva/pull/2697 - - * Enhancement #3370: Enable space members to list shares inside the space - - If there are shared resources in a space then all members are allowed to see those shares. The - json share manager was enhanced to check if the user is allowed to see a share by checking the - grants on a resource. - - https://github.com/owncloud/ocis/issues/3370 - https://github.com/cs3org/reva/pull/2674 - https://github.com/cs3org/reva/pull/2710 - - * Enhancement #2717: Add definitions for user and group events - - Enhance the events package with definitions for user and group events. - - https://github.com/cs3org/reva/pull/2717 - https://github.com/cs3org/reva/pull/2724 + https://github.com/cs3org/reva/pull/2278 diff --git a/changelog/unreleased/app-editnew.md b/changelog/unreleased/app-editnew.md new file mode 100644 index 0000000000..2f1f72e728 --- /dev/null +++ b/changelog/unreleased/app-editnew.md @@ -0,0 +1,5 @@ +Bugfix: Support editnew actions from MS Office + +This fixes the incorrect behavior when creating new xlsx and pptx files, as MS Office supports the editnew action and it must be used for newly created files instead of the normal edit action. + +https://github.com/cs3org/reva/pull/2693 diff --git a/changelog/unreleased/capabilities-public.md b/changelog/unreleased/capabilities-public.md new file mode 100644 index 0000000000..0cb5029340 --- /dev/null +++ b/changelog/unreleased/capabilities-public.md @@ -0,0 +1,3 @@ +Enhancement: Make capabilities endpoint public, authenticate users is present + +https://github.com/cs3org/reva/pull/2698 \ No newline at end of file diff --git a/changelog/unreleased/cephfs-fix-base.md b/changelog/unreleased/cephfs-fix-base.md new file mode 100644 index 0000000000..ac4e5de944 --- /dev/null +++ b/changelog/unreleased/cephfs-fix-base.md @@ -0,0 +1,6 @@ +Bugfix: Dockerfile.revad-ceph to use the right base image + +In Aug2021 https://hub.docker.com/r/ceph/daemon-base was moved to quay.ceph.io +and the builds for this image were failing for some weeks after January. + +https://github.com/cs3org/reva/pull/2588 diff --git a/changelog/unreleased/change-public-link-hash.md b/changelog/unreleased/change-public-link-hash.md new file mode 100644 index 0000000000..7b083922c2 --- /dev/null +++ b/changelog/unreleased/change-public-link-hash.md @@ -0,0 +1,7 @@ +Change: Remove hash from public link urls + +Public link urls do not contain the hash anymore, this is needed to support the ocis and web history mode. + +https://github.com/cs3org/reva/pull/2596 +https://github.com/owncloud/ocis/pull/3109 +https://github.com/owncloud/web/pull/6363 diff --git a/changelog/unreleased/enable-default-traces.md b/changelog/unreleased/enable-default-traces.md new file mode 100644 index 0000000000..9b96930b92 --- /dev/null +++ b/changelog/unreleased/enable-default-traces.md @@ -0,0 +1,3 @@ +Enhancement: Enabling tracing by default if not explicitly disabled + +https://github.com/cs3org/reva/pull/2515 \ No newline at end of file diff --git a/changelog/unreleased/eos-fix-deny-grant.md b/changelog/unreleased/eos-fix-deny-grant.md new file mode 100644 index 0000000000..1c06336164 --- /dev/null +++ b/changelog/unreleased/eos-fix-deny-grant.md @@ -0,0 +1,4 @@ +Bugfix: Removed check DenyGrant in resource permission +when adding a denial permission + +https://github.com/cs3org/reva/pull/2499 \ No newline at end of file diff --git a/changelog/unreleased/eos-scope-devs.md b/changelog/unreleased/eos-scope-devs.md new file mode 100644 index 0000000000..a5eeee678d --- /dev/null +++ b/changelog/unreleased/eos-scope-devs.md @@ -0,0 +1,3 @@ +Enhancement: Features for favorites xattrs in EOS, cache for scope expansion + +https://github.com/cs3org/reva/pull/2686 \ No newline at end of file diff --git a/changelog/unreleased/eos-sys-acl-files.md b/changelog/unreleased/eos-sys-acl-files.md new file mode 100644 index 0000000000..3836fa6830 --- /dev/null +++ b/changelog/unreleased/eos-sys-acl-files.md @@ -0,0 +1,3 @@ +Enhancement: Use sys ACLs for file permissions + +https://github.com/cs3org/reva/pull/2494 \ No newline at end of file diff --git a/changelog/unreleased/events.md b/changelog/unreleased/events.md new file mode 100644 index 0000000000..ca94ba2e8d --- /dev/null +++ b/changelog/unreleased/events.md @@ -0,0 +1,6 @@ +Enhancement: introduce events + +This will introduce events into the system. Events are a simple way to bring information from +one service to another. Read `pkg/events/example` and subfolders for more information + +https://github.com/cs3org/reva/pull/2522 diff --git a/changelog/unreleased/federated-accounts.md b/changelog/unreleased/federated-accounts.md new file mode 100644 index 0000000000..3129b0c1c9 --- /dev/null +++ b/changelog/unreleased/federated-accounts.md @@ -0,0 +1,3 @@ +Enhancement: Enable federated account access + +https://github.com/cs3org/reva/pull/2685 \ No newline at end of file diff --git a/changelog/unreleased/fix-ocmd-tutorial.md b/changelog/unreleased/fix-ocmd-tutorial.md new file mode 100644 index 0000000000..665eb53df9 --- /dev/null +++ b/changelog/unreleased/fix-ocmd-tutorial.md @@ -0,0 +1,7 @@ +Bugfix: Accept new userid idp format + +The format for userid idp [changed](https://github.com/cs3org/cs3apis/pull/159) +and this broke [the ocmd tutorial](https://reva.link/docs/tutorials/share-tutorial/#5-1-4-create-the-share) +This PR makes the provider authorizer interceptor accept both the old and the new string format. + +See https://github.com/cs3org/reva/issues/2285 and https://github.com/cs3org/reva/issues/2285 diff --git a/changelog/unreleased/honour-tracing-service-name.md b/changelog/unreleased/honour-tracing-service-name.md new file mode 100644 index 0000000000..b6f2a11b19 --- /dev/null +++ b/changelog/unreleased/honour-tracing-service-name.md @@ -0,0 +1,3 @@ +Bugfix: respect the tracing_service_name config variable + +https://github.com/cs3org/reva/pull/2608 diff --git a/changelog/unreleased/http-tpc.md b/changelog/unreleased/http-tpc.md new file mode 100644 index 0000000000..dd1f80c4dd --- /dev/null +++ b/changelog/unreleased/http-tpc.md @@ -0,0 +1,15 @@ +Enhancement: Add support for HTTP TPC + +We have added support for HTTP Third Party Copy. +This allows remote data transfers between storages managed by either two different reva servers, +or a reva server and a Grid (WLCG/ESCAPE) site server. + +Such remote transfers are expected to be driven by [GFAL](https://cern.ch/dmc-docs/gfal2/gfal2.html), +the underlying library used by [FTS](https://cern.ch/fts), and [Rucio](https://rucio.cern.ch). + +In addition, the oidcmapping package has been refactored to +support the standard OIDC use cases as well when no mapping +is defined. + +https://github.com/cs3org/reva/issues/1787 +https://github.com/cs3org/reva/pull/2007 diff --git a/changelog/unreleased/mentix-prom-ext.md b/changelog/unreleased/mentix-prom-ext.md new file mode 100644 index 0000000000..03df3192d4 --- /dev/null +++ b/changelog/unreleased/mentix-prom-ext.md @@ -0,0 +1,5 @@ +Enhancement: Mentix PromSD extensions + +The Mentix Prometheus SD scrape targets are now split into one file per service type, making health checks configuration easier. Furthermore, the local file connector for mesh data and the site registration endpoint have been dropped, as they aren't needed anymore. + +https://github.com/cs3org/reva/pull/2560 diff --git a/changelog/unreleased/mimetypes-config.md b/changelog/unreleased/mimetypes-config.md new file mode 100644 index 0000000000..680115abaa --- /dev/null +++ b/changelog/unreleased/mimetypes-config.md @@ -0,0 +1,5 @@ +Enhancement: Externalize custom mime types configuration for storage providers + +Added ability to configure custom mime types in an external JSON file, such that it can be reused when many storage providers are deployed at the same time. + +https://github.com/cs3org/reva/pull/2613 diff --git a/changelog/unreleased/nextcloud-ocm-share-manager.md b/changelog/unreleased/nextcloud-ocm-share-manager.md new file mode 100644 index 0000000000..0d20e86dbb --- /dev/null +++ b/changelog/unreleased/nextcloud-ocm-share-manager.md @@ -0,0 +1,7 @@ +Enhancement: Nextcloud-based share manager for pkg/ocm/share + +Note that pkg/ocm/share is very similar to pkg/share, +but it deals with cs3/sharing/ocm +whereas pkg/share deals with cs3/sharing/collaboration + +https://github.com/cs3org/reva/pull/2163 diff --git a/changelog/unreleased/preferences-refactor.md b/changelog/unreleased/preferences-refactor.md new file mode 100644 index 0000000000..a15d1fbcdf --- /dev/null +++ b/changelog/unreleased/preferences-refactor.md @@ -0,0 +1,7 @@ +Enhancement: Preferences driver refactor and cbox sql implementation + +This PR uses the updated CS3APIs which accepts a namespace in addition to a +single string key to recognize a user preference. It also refactors the GRPC +service to support multiple drivers and adds the cbox SQL implementation. + +https://github.com/cs3org/reva/pull/2696 \ No newline at end of file diff --git a/changelog/unreleased/pull-transfer.md b/changelog/unreleased/pull-transfer.md new file mode 100644 index 0000000000..276609dd63 --- /dev/null +++ b/changelog/unreleased/pull-transfer.md @@ -0,0 +1,6 @@ +Enhancement: New CS3API datatx methods + +CS3 datatx pull model methods: PullTransfer, RetryTransfer, ListTransfers +Method CreateTransfer removed. + +https://github.com/cs3org/reva/pull/2052 \ No newline at end of file diff --git a/changelog/unreleased/remove-base64-encoding-of-ids.md b/changelog/unreleased/remove-base64-encoding-of-ids.md new file mode 100644 index 0000000000..f611bc879a --- /dev/null +++ b/changelog/unreleased/remove-base64-encoding-of-ids.md @@ -0,0 +1,5 @@ +Change: Do not encode webDAV ids to base64 + +We removed the base64 encoding of the IDs and use the format ! with a `!` delimiter. As a reserved delimiter it is URL safe. The IDs will be XML and JSON encoded as necessary. + +https://github.com/cs3org/reva/pull/2559 \ No newline at end of file diff --git a/changelog/unreleased/rfc3339-fix.md b/changelog/unreleased/rfc3339-fix.md new file mode 100644 index 0000000000..a448d80e8c --- /dev/null +++ b/changelog/unreleased/rfc3339-fix.md @@ -0,0 +1,6 @@ +Bugfix: Use RFC3339 for parsing dates + +We have used the RFC3339 format for parsing dates to be consistent with oC Web. + +https://github.com/cs3org/reva/issues/2322 +https://github.com/cs3org/reva/pull/2744 \ No newline at end of file diff --git a/changelog/unreleased/siteacc-fix.md b/changelog/unreleased/siteacc-fix.md new file mode 100644 index 0000000000..3f5a4d8866 --- /dev/null +++ b/changelog/unreleased/siteacc-fix.md @@ -0,0 +1,5 @@ +Bugfix: Fix site accounts endpoints + +This PR fixes small bugs in the site accounts endpoints. + +https://github.com/cs3org/reva/pull/2555 diff --git a/changelog/unreleased/siteacc-site-settings.md b/changelog/unreleased/siteacc-site-settings.md new file mode 100644 index 0000000000..8a3a6c2468 --- /dev/null +++ b/changelog/unreleased/siteacc-site-settings.md @@ -0,0 +1,5 @@ +Enhancement: Site accounts site-global settings + +This PR extends the site accounts service by adding site-global settings. These are used to store test user credentials that are in return used by our BBE port to perform CS3API-specific health checks. + +https://github.com/cs3org/reva/pull/2738 diff --git a/changelog/unreleased/siteacc-upd-2.md b/changelog/unreleased/siteacc-upd-2.md new file mode 100644 index 0000000000..b270b1a045 --- /dev/null +++ b/changelog/unreleased/siteacc-upd-2.md @@ -0,0 +1,9 @@ +Enhancement: Further Site Accounts improvements + +Yet another PR to update the site accounts (and Mentix): +New default site ID; +Include service type in alerts; +Naming unified; +Remove obsolete stuff. + +https://github.com/cs3org/reva/pull/2672 diff --git a/changelog/unreleased/siteacc-upd.md b/changelog/unreleased/siteacc-upd.md new file mode 100644 index 0000000000..4fd2ffb052 --- /dev/null +++ b/changelog/unreleased/siteacc-upd.md @@ -0,0 +1,8 @@ +Enhancement: Site accounts improvements + +This PR improves the site accounts: +- Removed/hid API key stuff +- Added quick links to the main panel +- Made alert notifications mandatory + +https://github.com/cs3org/reva/pull/2549 diff --git a/changelog/unreleased/unify-oidc.md b/changelog/unreleased/unify-oidc.md new file mode 100644 index 0000000000..182bfbab6b --- /dev/null +++ b/changelog/unreleased/unify-oidc.md @@ -0,0 +1,7 @@ +Change: Merge oidcmapping auth manager into oidc + +The oidcmapping auth manager was created as a separate package to ease testing. As it has now been tested +also as a pure OIDC auth provider without mapping, and as the code is largely refactored, it makes +sense to merge it back so to maintain a single OIDC manager. + +https://github.com/cs3org/reva/pull/2561 diff --git a/changelog/unreleased/update-makefile.md b/changelog/unreleased/update-makefile.md new file mode 100644 index 0000000000..89816abd16 --- /dev/null +++ b/changelog/unreleased/update-makefile.md @@ -0,0 +1,7 @@ +Bugfix: Updates Makefile according to latest go standards + +Earlier, we were using go get to install packages. +Now, we are using go install to install packages + +https://github.com/cs3org/reva/issues/2675 +https://github.com/cs3org/reva/pull/2747 \ No newline at end of file diff --git a/changelog/unreleased/waitForNats.md b/changelog/unreleased/waitForNats.md new file mode 100644 index 0000000000..d4463dd695 --- /dev/null +++ b/changelog/unreleased/waitForNats.md @@ -0,0 +1,5 @@ +Bugfix: wait for nats server on middleware start + +Use a retry mechanism to connect to the nats server when it is not ready yet + +https://github.com/cs3org/reva/pull/2572 diff --git a/cmd/reva/common.go b/cmd/reva/common.go index ed630c1128..64f80ac566 100644 --- a/cmd/reva/common.go +++ b/cmd/reva/common.go @@ -31,7 +31,6 @@ import ( const ( viewerPermission string = "viewer" - readerPermission string = "reader" editorPermission string = "editor" collabPermission string = "collab" denyPermission string = "denied" diff --git a/cmd/reva/main.go b/cmd/reva/main.go index c46bd27f53..96c84416cc 100644 --- a/cmd/reva/main.go +++ b/cmd/reva/main.go @@ -82,6 +82,8 @@ var ( transferCreateCommand(), transferGetStatusCommand(), transferCancelCommand(), + transferListCommand(), + transferRetryCommand(), appTokensListCommand(), appTokensRemoveCommand(), appTokensCreateCommand(), diff --git a/cmd/reva/preferences.go b/cmd/reva/preferences.go index dff5e475d1..51471950bd 100644 --- a/cmd/reva/preferences.go +++ b/cmd/reva/preferences.go @@ -40,6 +40,7 @@ var preferencesCommand = func() *command { subcommand := cmd.Args()[0] key := cmd.Args()[1] + ns := cmd.Args()[2] client, err := getClient() if err != nil { @@ -50,12 +51,15 @@ var preferencesCommand = func() *command { switch subcommand { case "set": - if cmd.NArg() < 3 { + if cmd.NArg() < 4 { return errors.New("Invalid arguments: " + cmd.Usage()) } - value := cmd.Args()[2] + value := cmd.Args()[3] req := &preferences.SetKeyRequest{ - Key: &preferences.PreferenceKey{Key: key}, + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, Val: value, } @@ -70,7 +74,10 @@ var preferencesCommand = func() *command { case "get": req := &preferences.GetKeyRequest{ - Key: &preferences.PreferenceKey{Key: key}, + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, } res, err := client.GetKey(ctx, req) diff --git a/cmd/reva/share-create.go b/cmd/reva/share-create.go index fe915ebd06..7042c73615 100644 --- a/cmd/reva/share-create.go +++ b/cmd/reva/share-create.go @@ -28,7 +28,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/pkg/utils" "github.com/jedib0t/go-pretty/table" "github.com/pkg/errors" ) @@ -158,48 +159,11 @@ func getGrantType(t string) provider.GranteeType { func getSharePerm(p string) (*provider.ResourcePermissions, error) { switch p { case viewerPermission: - return &provider.ResourcePermissions{ - GetPath: true, - ListContainer: true, - Stat: true, - }, nil - case readerPermission: - return &provider.ResourcePermissions{ - GetPath: true, - InitiateFileDownload: true, - ListFileVersions: true, - ListContainer: true, - Stat: true, - }, nil + return conversions.NewViewerRole().CS3ResourcePermissions(), nil case editorPermission: - return &provider.ResourcePermissions{ - GetPath: true, - InitiateFileDownload: true, - ListFileVersions: true, - ListContainer: true, - Stat: true, - CreateContainer: true, - Delete: true, - InitiateFileUpload: true, - RestoreFileVersion: true, - Move: true, - }, nil + return conversions.NewEditorRole().CS3ResourcePermissions(), nil case collabPermission: - return &provider.ResourcePermissions{ - GetPath: true, - InitiateFileDownload: true, - ListFileVersions: true, - ListContainer: true, - Stat: true, - CreateContainer: true, - Delete: true, - InitiateFileUpload: true, - RestoreFileVersion: true, - Move: true, - AddGrant: true, - UpdateGrant: true, - RemoveGrant: true, - }, nil + return conversions.NewCoownerRole().CS3ResourcePermissions(), nil case denyPermission: return &provider.ResourcePermissions{}, nil default: diff --git a/cmd/reva/transfer-cancel.go b/cmd/reva/transfer-cancel.go index aafcce950c..dfa086467b 100644 --- a/cmd/reva/transfer-cancel.go +++ b/cmd/reva/transfer-cancel.go @@ -19,23 +19,27 @@ package main import ( + "encoding/gob" "errors" "io" + "os" + "time" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + "github.com/jedib0t/go-pretty/table" ) func transferCancelCommand() *command { cmd := newCommand("transfer-cancel") cmd.Description = func() string { return "cancel a running transfer" } cmd.Usage = func() string { return "Usage: transfer-cancel [-flags]" } - txID := cmd.String("txID", "", "the transfer identifier") + txID := cmd.String("txId", "", "the transfer identifier") cmd.Action = func(w ...io.Writer) error { // validate flags if *txID == "" { - return errors.New("txID must be specified: use -txID flag\n" + cmd.Usage()) + return errors.New("txId must be specified: use -txId flag\n" + cmd.Usage()) } ctx := getAuthContext() @@ -44,7 +48,9 @@ func transferCancelCommand() *command { return err } - cancelRequest := &datatx.CancelTransferRequest{} + cancelRequest := &datatx.CancelTransferRequest{ + TxId: &datatx.TxId{OpaqueId: *txID}, + } cancelResponse, err := client.CancelTransfer(ctx, cancelRequest) if err != nil { @@ -54,6 +60,22 @@ func transferCancelCommand() *command { return formatError(cancelResponse.Status) } + if len(w) == 0 { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ShareId.OpaqueId", "Id.OpaqueId", "Status", "Ctime"}) + cTime := time.Unix(int64(cancelResponse.TxInfo.Ctime.Seconds), int64(cancelResponse.TxInfo.Ctime.Nanos)) + t.AppendRows([]table.Row{ + {cancelResponse.TxInfo.ShareId.OpaqueId, cancelResponse.TxInfo.Id.OpaqueId, cancelResponse.TxInfo.Status, cTime.Format("Mon Jan 2 15:04:05 -0700 MST 2006")}, + }) + t.Render() + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(cancelResponse.TxInfo); err != nil { + return err + } + } + return nil } return cmd diff --git a/cmd/reva/transfer-create.go b/cmd/reva/transfer-create.go index be0666d2dd..7902a3281a 100644 --- a/cmd/reva/transfer-create.go +++ b/cmd/reva/transfer-create.go @@ -42,8 +42,8 @@ func transferCreateCommand() *command { cmd.Description = func() string { return "create transfer between 2 sites" } cmd.Usage = func() string { return "Usage: transfer-create [-flags] " } grantee := cmd.String("grantee", "", "the grantee, receiver of the transfer") - granteeType := cmd.String("granteeType", "user", "the grantee type, one of: user, group") - idp := cmd.String("idp", "", "the idp of the grantee, default to same idp as the user triggering the action") + granteeType := cmd.String("granteeType", "user", "the grantee type, one of: user, group (defaults to user)") + idp := cmd.String("idp", "", "the idp of the grantee") userType := cmd.String("user-type", "primary", "the type of user account, defaults to primary") cmd.Action = func(w ...io.Writer) error { diff --git a/cmd/reva/transfer-get-status.go b/cmd/reva/transfer-get-status.go index bef584c913..ab73a52e69 100644 --- a/cmd/reva/transfer-get-status.go +++ b/cmd/reva/transfer-get-status.go @@ -19,23 +19,27 @@ package main import ( + "encoding/gob" "errors" "io" + "os" + "time" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + "github.com/jedib0t/go-pretty/table" ) func transferGetStatusCommand() *command { cmd := newCommand("transfer-get-status") cmd.Description = func() string { return "get the status of a transfer" } cmd.Usage = func() string { return "Usage: transfer-get-status [-flags]" } - txID := cmd.String("txID", "", "the transfer identifier") + txID := cmd.String("txId", "", "the transfer identifier") cmd.Action = func(w ...io.Writer) error { // validate flags if *txID == "" { - return errors.New("txID must be specified: use -txID flag\n" + cmd.Usage()) + return errors.New("txId must be specified: use -txId flag\n" + cmd.Usage()) } ctx := getAuthContext() @@ -44,7 +48,9 @@ func transferGetStatusCommand() *command { return err } - getStatusRequest := &datatx.GetTransferStatusRequest{} + getStatusRequest := &datatx.GetTransferStatusRequest{ + TxId: &datatx.TxId{OpaqueId: *txID}, + } getStatusResponse, err := client.GetTransferStatus(ctx, getStatusRequest) if err != nil { @@ -54,6 +60,22 @@ func transferGetStatusCommand() *command { return formatError(getStatusResponse.Status) } + if len(w) == 0 { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ShareId.OpaqueId", "Id.OpaqueId", "Status", "Ctime"}) + cTime := time.Unix(int64(getStatusResponse.TxInfo.Ctime.Seconds), int64(getStatusResponse.TxInfo.Ctime.Nanos)) + t.AppendRows([]table.Row{ + {getStatusResponse.TxInfo.ShareId.OpaqueId, getStatusResponse.TxInfo.Id.OpaqueId, getStatusResponse.TxInfo.Status, cTime.Format("Mon Jan 2 15:04:05 -0700 MST 2006")}, + }) + t.Render() + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(getStatusResponse.TxInfo); err != nil { + return err + } + } + return nil } return cmd diff --git a/cmd/reva/transfer-list.go b/cmd/reva/transfer-list.go new file mode 100644 index 0000000000..b0ea83ae9d --- /dev/null +++ b/cmd/reva/transfer-list.go @@ -0,0 +1,91 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "encoding/gob" + "io" + "os" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + "github.com/jedib0t/go-pretty/table" +) + +func transferListCommand() *command { + cmd := newCommand("transfer-list") + cmd.Description = func() string { return "get a list of transfers" } + cmd.Usage = func() string { return "Usage: transfer-list [-flags]" } + filterShareID := cmd.String("shareId", "", "share ID filter (optional)") + + cmd.Action = func(w ...io.Writer) error { + ctx := getAuthContext() + client, err := getClient() + if err != nil { + return err + } + + // validate flags + var filters []*datatx.ListTransfersRequest_Filter + if *filterShareID != "" { + filters = append(filters, &datatx.ListTransfersRequest_Filter{ + Type: datatx.ListTransfersRequest_Filter_TYPE_SHARE_ID, + Term: &datatx.ListTransfersRequest_Filter_ShareId{ + ShareId: &ocm.ShareId{ + OpaqueId: *filterShareID, + }, + }, + }) + } + + transferslistRequest := &datatx.ListTransfersRequest{ + Filters: filters, + } + + listTransfersResponse, err := client.ListTransfers(ctx, transferslistRequest) + if err != nil { + return err + } + if listTransfersResponse.Status.Code != rpc.Code_CODE_OK { + return formatError(listTransfersResponse.Status) + } + + if len(w) == 0 { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ShareId.OpaqueId", "Id.OpaqueId"}) + + for _, s := range listTransfersResponse.Transfers { + t.AppendRows([]table.Row{ + {s.ShareId.OpaqueId, s.Id.OpaqueId}, + }) + } + t.Render() + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(listTransfersResponse.Transfers); err != nil { + return err + } + } + + return nil + } + return cmd +} diff --git a/cmd/reva/transfer-retry.go b/cmd/reva/transfer-retry.go new file mode 100644 index 0000000000..2f2ada08d9 --- /dev/null +++ b/cmd/reva/transfer-retry.go @@ -0,0 +1,84 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "encoding/gob" + "errors" + "io" + "os" + "time" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + "github.com/jedib0t/go-pretty/table" +) + +func transferRetryCommand() *command { + cmd := newCommand("transfer-retry") + cmd.Description = func() string { return "retry a transfer" } + cmd.Usage = func() string { return "Usage: transfer-retry [-flags]" } + txID := cmd.String("txId", "", "the transfer identifier") + + cmd.Action = func(w ...io.Writer) error { + // validate flags + if *txID == "" { + return errors.New("txId must be specified: use -txId flag\n" + cmd.Usage()) + } + + ctx := getAuthContext() + client, err := getClient() + if err != nil { + return err + } + + retryRequest := &datatx.RetryTransferRequest{ + TxId: &datatx.TxId{ + OpaqueId: *txID, + }, + } + + retryResponse, err := client.RetryTransfer(ctx, retryRequest) + if err != nil { + return err + } + if retryResponse.Status.Code != rpc.Code_CODE_OK { + return formatError(retryResponse.Status) + } + + if len(w) == 0 { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"ShareId.OpaqueId", "Id.OpaqueId", "Status", "Ctime"}) + cTime := time.Unix(int64(retryResponse.TxInfo.Ctime.Seconds), int64(retryResponse.TxInfo.Ctime.Nanos)) + t.AppendRows([]table.Row{ + {retryResponse.TxInfo.ShareId.OpaqueId, retryResponse.TxInfo.Id.OpaqueId, retryResponse.TxInfo.Status, cTime}, + }) + t.Render() + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(retryResponse.TxInfo); err != nil { + return err + } + } + + return nil + } + return cmd +} diff --git a/cmd/revad/runtime/loader.go b/cmd/revad/runtime/loader.go index 4f2f3830a8..fc917052a9 100644 --- a/cmd/revad/runtime/loader.go +++ b/cmd/revad/runtime/loader.go @@ -20,32 +20,35 @@ package runtime import ( // These are all the extensions points for REVA - _ "github.com/cs3org/reva/v2/internal/grpc/interceptors/loader" - _ "github.com/cs3org/reva/v2/internal/grpc/services/loader" - _ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/loader" - _ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/token/loader" - _ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/tokenwriter/loader" - _ "github.com/cs3org/reva/v2/internal/http/interceptors/loader" - _ "github.com/cs3org/reva/v2/internal/http/services/loader" - _ "github.com/cs3org/reva/v2/pkg/app/provider/loader" - _ "github.com/cs3org/reva/v2/pkg/app/registry/loader" - _ "github.com/cs3org/reva/v2/pkg/appauth/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/auth/registry/loader" - _ "github.com/cs3org/reva/v2/pkg/cbox/loader" - _ "github.com/cs3org/reva/v2/pkg/group/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/metrics/driver/loader" - _ "github.com/cs3org/reva/v2/pkg/ocm/invite/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/loader" - _ "github.com/cs3org/reva/v2/pkg/ocm/share/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/permission/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/publicshare/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/rhttp/datatx/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/share/cache/loader" - _ "github.com/cs3org/reva/v2/pkg/share/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/storage/favorite/loader" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/loader" - _ "github.com/cs3org/reva/v2/pkg/storage/registry/loader" - _ "github.com/cs3org/reva/v2/pkg/token/manager/loader" - _ "github.com/cs3org/reva/v2/pkg/user/manager/loader" + _ "github.com/cs3org/reva/internal/grpc/interceptors/loader" + _ "github.com/cs3org/reva/internal/grpc/services/loader" + _ "github.com/cs3org/reva/internal/http/interceptors/auth/credential/loader" + _ "github.com/cs3org/reva/internal/http/interceptors/auth/token/loader" + _ "github.com/cs3org/reva/internal/http/interceptors/auth/tokenwriter/loader" + _ "github.com/cs3org/reva/internal/http/interceptors/loader" + _ "github.com/cs3org/reva/internal/http/services/loader" + _ "github.com/cs3org/reva/pkg/app/provider/loader" + _ "github.com/cs3org/reva/pkg/app/registry/loader" + _ "github.com/cs3org/reva/pkg/appauth/manager/loader" + _ "github.com/cs3org/reva/pkg/auth/manager/loader" + _ "github.com/cs3org/reva/pkg/auth/registry/loader" + _ "github.com/cs3org/reva/pkg/cbox/loader" + _ "github.com/cs3org/reva/pkg/datatx/manager/loader" + _ "github.com/cs3org/reva/pkg/group/manager/loader" + _ "github.com/cs3org/reva/pkg/metrics/driver/loader" + _ "github.com/cs3org/reva/pkg/ocm/invite/manager/loader" + _ "github.com/cs3org/reva/pkg/ocm/provider/authorizer/loader" + _ "github.com/cs3org/reva/pkg/ocm/share/manager/loader" + _ "github.com/cs3org/reva/pkg/permission/manager/loader" + _ "github.com/cs3org/reva/pkg/preferences/loader" + _ "github.com/cs3org/reva/pkg/publicshare/manager/loader" + _ "github.com/cs3org/reva/pkg/rhttp/datatx/manager/loader" + _ "github.com/cs3org/reva/pkg/share/cache/loader" + _ "github.com/cs3org/reva/pkg/share/cache/warmup/loader" + _ "github.com/cs3org/reva/pkg/share/manager/loader" + _ "github.com/cs3org/reva/pkg/storage/favorite/loader" + _ "github.com/cs3org/reva/pkg/storage/fs/loader" + _ "github.com/cs3org/reva/pkg/storage/registry/loader" + _ "github.com/cs3org/reva/pkg/token/manager/loader" + _ "github.com/cs3org/reva/pkg/user/manager/loader" ) diff --git a/cmd/revad/runtime/runtime.go b/cmd/revad/runtime/runtime.go index 8535cb65b3..18bd08143a 100644 --- a/cmd/revad/runtime/runtime.go +++ b/cmd/revad/runtime/runtime.go @@ -149,7 +149,7 @@ func initServers(mainConf map[string]interface{}, log *zerolog.Logger) map[strin } func initTracing(conf *coreConf) { - rtrace.SetTraceProvider(conf.TracingCollector, conf.TracingEndpoint) + rtrace.SetTraceProvider(conf.TracingCollector, conf.TracingEndpoint, conf.TracingServiceName) } func initCPUCount(conf *coreConf, log *zerolog.Logger) { @@ -311,6 +311,15 @@ func parseCoreConfOrDie(v interface{}) *coreConf { os.Exit(1) } + // tracing defaults to enabled if not explicitly configured + if v == nil { + c.TracingEnabled = true + c.TracingEndpoint = "localhost:6831" + } else if _, ok := v.(map[string]interface{})["tracing_enabled"]; !ok { + c.TracingEnabled = true + c.TracingEndpoint = "localhost:6831" + } + return c } diff --git a/composer.json b/composer.json index baf6cc33f1..9de39b38ec 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "config" : { "platform": { "php": "7.4" - } + }, + "vendor-dir": "./vendor-php" }, "require": { }, diff --git a/docs/content/en/docs/changelog/1.18.0/_index.md b/docs/content/en/docs/changelog/1.18.0/_index.md new file mode 100644 index 0000000000..511a19bfd9 --- /dev/null +++ b/docs/content/en/docs/changelog/1.18.0/_index.md @@ -0,0 +1,144 @@ + +--- +title: "v1.18.0" +linkTitle: "v1.18.0" +weight: 40 +description: > + Changelog for Reva v1.18.0 (2022-02-11) +--- + +Changelog for reva 1.18.0 (2022-02-11) +======================================= + +The following sections list the changes in reva 1.18.0 relevant to +reva users. The changes are ordered by importance. + +Summary +------- + + * Fix #2370: Fixes for apps in public shares, project spaces for EOS driver + * Fix #2374: Fix webdav copy of zero byte files + * Fix #2478: Use ocs permission objects in the reva GRPC client + * Fix #2368: Return wrapped paths for recycled items in storage provider + * Chg #2354: Return not found when updating non existent space + * Enh #1209: Reva CephFS module v0.2.1 + * Enh #2341: Use CS3 permissions API + * Enh #2350: Add file locking methods to the storage and filesystem interfaces + * Enh #2379: Add new file url of the app provider to the ocs capabilities + * Enh #2369: Implement TouchFile from the CS3apis + * Enh #2385: Allow to create new files with the app provider on public links + * Enh #2397: Product field in OCS version + * Enh #2393: Update tus/tusd to version 1.8.0 + * Enh #2205: Modify group and user managers to skip fetching specified metadata + * Enh #2232: Make ocs resource info cache interoperable across drivers + * Enh #2233: Populate owner data in the ocs and ocdav services + * Enh #2278: OIDC driver changes for lightweight users + +Details +------- + + * Bugfix #2370: Fixes for apps in public shares, project spaces for EOS driver + + https://github.com/cs3org/reva/pull/2370 + + * Bugfix #2374: Fix webdav copy of zero byte files + + We've fixed the webdav copy action of zero byte files, which was not performed because the + webdav api assumed, that zero byte uploads are created when initiating the upload, which was + recently removed from all storage drivers. Therefore the webdav api also uploads zero byte + files after initiating the upload. + + https://github.com/cs3org/reva/pull/2374 + https://github.com/cs3org/reva/pull/2309 + + * Bugfix #2478: Use ocs permission objects in the reva GRPC client + + There was a bug introduced by differing CS3APIs permission definitions for the same role + across services. This is a first step in making all services use consistent definitions. + + https://github.com/cs3org/reva/pull/2478 + + * Bugfix #2368: Return wrapped paths for recycled items in storage provider + + https://github.com/cs3org/reva/pull/2368 + + * Change #2354: Return not found when updating non existent space + + If a spaceid of a space which is updated doesn't exist, handle it as a not found error. + + https://github.com/cs3org/reva/pull/2354 + + * Enhancement #1209: Reva CephFS module v0.2.1 + + https://github.com/cs3org/reva/pull/1209 + + * Enhancement #2341: Use CS3 permissions API + + Added calls to the CS3 permissions API to the decomposedfs in order to check the user + permissions. + + https://github.com/cs3org/reva/pull/2341 + + * Enhancement #2350: Add file locking methods to the storage and filesystem interfaces + + We've added the file locking methods from the CS3apis to the storage and filesystem + interfaces. As of now they are dummy implementations and will only return "unimplemented" + errors. + + https://github.com/cs3org/reva/pull/2350 + https://github.com/cs3org/cs3apis/pull/160 + + * Enhancement #2379: Add new file url of the app provider to the ocs capabilities + + We've added the new file capability of the app provider to the ocs capabilities, so that clients + can discover this url analogous to the app list and file open urls. + + https://github.com/cs3org/reva/pull/2379 + https://github.com/owncloud/ocis/pull/2884 + https://github.com/owncloud/web/pull/5890#issuecomment-993905242 + + * Enhancement #2369: Implement TouchFile from the CS3apis + + We've updated the CS3apis and implemented the TouchFile method. + + https://github.com/cs3org/reva/pull/2369 + https://github.com/cs3org/cs3apis/pull/154 + + * Enhancement #2385: Allow to create new files with the app provider on public links + + We've added the option to create files with the app provider on public links. + + https://github.com/cs3org/reva/pull/2385 + + * Enhancement #2397: Product field in OCS version + + We've added a new field to the OCS Version, which is supposed to announce the product name. The + web ui as a client will make use of it to make the backend product and version available (e.g. for + easier bug reports). + + https://github.com/cs3org/reva/pull/2397 + + * Enhancement #2393: Update tus/tusd to version 1.8.0 + + We've update tus/tusd to version 1.8.0. + + https://github.com/cs3org/reva/issues/2393 + https://github.com/cs3org/reva/pull/2224 + + * Enhancement #2205: Modify group and user managers to skip fetching specified metadata + + https://github.com/cs3org/reva/pull/2205 + + * Enhancement #2232: Make ocs resource info cache interoperable across drivers + + https://github.com/cs3org/reva/pull/2232 + + * Enhancement #2233: Populate owner data in the ocs and ocdav services + + https://github.com/cs3org/reva/pull/2233 + + * Enhancement #2278: OIDC driver changes for lightweight users + + https://github.com/cs3org/reva/pull/2278 + + diff --git a/docs/content/en/docs/config/grpc/services/storageprovider/_index.md b/docs/content/en/docs/config/grpc/services/storageprovider/_index.md index b4d7ddf287..0a9ff8c468 100644 --- a/docs/content/en/docs/config/grpc/services/storageprovider/_index.md +++ b/docs/content/en/docs/config/grpc/services/storageprovider/_index.md @@ -8,8 +8,24 @@ description: > # _struct: config_ +{{% dir name="mount_path" type="string" default="/" %}} +The path where the file system would be mounted. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L58) +{{< highlight toml >}} +[grpc.services.storageprovider] +mount_path = "/" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="mount_id" type="string" default="-" %}} +The ID of the mounted file system. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L59) +{{< highlight toml >}} +[grpc.services.storageprovider] +mount_id = "-" +{{< /highlight >}} +{{% /dir %}} + {{% dir name="driver" type="string" default="localhome" %}} -The storage driver to be used. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L55) +The storage driver to be used. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L60) {{< highlight toml >}} [grpc.services.storageprovider] driver = "localhome" @@ -17,7 +33,7 @@ driver = "localhome" {{% /dir %}} {{% dir name="drivers" type="map[string]map[string]interface{}" default="localhome" %}} - [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L56) + [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L61) {{< highlight toml >}} [grpc.services.storageprovider.drivers.localhome] root = "/var/tmp/reva/" @@ -28,7 +44,7 @@ user_layout = "{{.Username}}" {{% /dir %}} {{% dir name="tmp_folder" type="string" default="/var/tmp" %}} -Path to temporary folder. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L57) +Path to temporary folder. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L62) {{< highlight toml >}} [grpc.services.storageprovider] tmp_folder = "/var/tmp" @@ -36,7 +52,7 @@ tmp_folder = "/var/tmp" {{% /dir %}} {{% dir name="data_server_url" type="string" default="http://localhost/data" %}} -The URL for the data server. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L58) +The URL for the data server. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L63) {{< highlight toml >}} [grpc.services.storageprovider] data_server_url = "http://localhost/data" @@ -44,7 +60,7 @@ data_server_url = "http://localhost/data" {{% /dir %}} {{% dir name="expose_data_server" type="bool" default=false %}} -Whether to expose data server. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L59) +Whether to expose data server. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L64) {{< highlight toml >}} [grpc.services.storageprovider] expose_data_server = false @@ -52,18 +68,18 @@ expose_data_server = false {{% /dir %}} {{% dir name="available_checksums" type="map[string]uint32" default=nil %}} -List of available checksums. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L60) +List of available checksums. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L65) {{< highlight toml >}} [grpc.services.storageprovider] available_checksums = nil {{< /highlight >}} {{% /dir %}} -{{% dir name="mimetypes" type="map[string]string" default=nil %}} -List of supported mime types and corresponding file extensions. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L61) +{{% dir name="custom_mimetypes_json" type="string" default="nil" %}} +An optional mapping file with the list of supported custom file extensions and corresponding mime types. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/storageprovider/storageprovider.go#L66) {{< highlight toml >}} [grpc.services.storageprovider] -mimetypes = nil +custom_mimetypes_json = "nil" {{< /highlight >}} {{% /dir %}} diff --git a/docs/content/en/docs/config/http/services/mentix/_index.md b/docs/content/en/docs/config/http/services/mentix/_index.md index dd73b4f51a..c71eb414da 100644 --- a/docs/content/en/docs/config/http/services/mentix/_index.md +++ b/docs/content/en/docs/config/http/services/mentix/_index.md @@ -43,17 +43,13 @@ _Supported connectors:_ - **gocdb** The [GOCDB](https://wiki.egi.eu/wiki/GOCDB/Documentation_Index) is a database specifically designed to organize the topology of a mesh of distributed sites and services. In order to use GOCDB with Mentix, its instance address has to be configured (see [here](gocdb)). - -- **localfile** -The [localfile](localfile) connector reads sites from a local JSON file. The file must contain an array of sites adhering to the `meshdata.Site` structure. ## Importers Mentix can import mesh data from various sources and write it to one or more targets through the corresponding connectors. __Supported importers:__ -- **sitereg** -Mentix can import new sites via an HTTP endpoint using the `sitereg` importer. Data can be sent to the configured relative endpoint (see [here](sitereg)). +- **None** ## Exporters Mentix exposes its gathered data by using one or more _exporters_. Such exporters can, for example, write the data to a file in a specific format, or offer the data via an HTTP endpoint. diff --git a/docs/content/en/docs/config/http/services/mentix/localfile/_index.md b/docs/content/en/docs/config/http/services/mentix/localfile/_index.md deleted file mode 100644 index 0a85c0059a..0000000000 --- a/docs/content/en/docs/config/http/services/mentix/localfile/_index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "localfile" -linkTitle: "localfile" -weight: 10 -description: > - Configuration for the local file connector of the Mentix service ---- - -{{% pageinfo %}} -The local file connector reads sites from a local JSON file adhering to the `meshdata.Site` structure. -{{% /pageinfo %}} - -{{% dir name="file" type="string" default="" %}} -The file path. -{{< highlight toml >}} -[http.services.mentix.connectors.localfile] -file = "/etc/reva/sites.json" -{{< /highlight >}} -{{% /dir %}} diff --git a/docs/content/en/docs/config/http/services/mentix/promsd/_index.md b/docs/content/en/docs/config/http/services/mentix/promsd/_index.md index 20cb66952a..4ff4a64528 100644 --- a/docs/content/en/docs/config/http/services/mentix/promsd/_index.md +++ b/docs/content/en/docs/config/http/services/mentix/promsd/_index.md @@ -10,19 +10,11 @@ description: > When using the Prometheus SD exporter, the output filenames have to be configured first. {{% /pageinfo %}} -{{% dir name="metrics_output_file" type="string" default="" %}} -The target filename of the generated Prometheus File SD scrape config for metrics. +{{% dir name="output_path" type="string" default="" %}} +The target path of the generated Prometheus File SD scrape configs for metrics. {{< highlight toml >}} [http.services.mentix.exporters.promsd] -metrics_output_file = "/var/shared/prometheus/sciencemesh.json" -{{< /highlight >}} -{{% /dir %}} - -{{% dir name="blackbox_output_file" type="string" default="" %}} -The target filename of the generated Prometheus File SD scrape config for the blackbox exporter. -{{< highlight toml >}} -[http.services.mentix.exporters.promsd] -blackbox_output_file = "/var/shared/prometheus/blackbox.json" +output_path = "/var/shared/prometheus/sciencemesh" {{< /highlight >}} {{% /dir %}} diff --git a/docs/content/en/docs/config/http/services/mentix/sitereg/_index.md b/docs/content/en/docs/config/http/services/mentix/sitereg/_index.md deleted file mode 100644 index 3591732b9e..0000000000 --- a/docs/content/en/docs/config/http/services/mentix/sitereg/_index.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "sitereg" -linkTitle: "sitereg" -weight: 10 -description: > - Configuration for site registration service ---- - -{{% pageinfo %}} -The site registration service is used to register new and unregister existing sites. -{{% /pageinfo %}} - -The site registration service is used to register new and unregister existing sites. - -{{% dir name="endpoint" type="string" default="/sitereg" %}} -The endpoint of the service. -{{< highlight toml >}} -[http.services.mentix.importers.sitereg] -endpoint = "/reg" -{{< /highlight >}} -{{% /dir %}} - -{{% dir name="enabled_connectors" type="[]string" default="" %}} -A list of all enabled connectors for the importer. -{{< highlight toml >}} -[http.services.mentix.importers.sitereg] -enabled_connectors = ["localfile"] -{{< /highlight >}} -{{% /dir %}} - -{{% dir name="ignore_sm_sites" type="bool" default="false" %}} -If set to true, registrations from ScienceMesh sites will be ignored. -{{< highlight toml >}} -[http.services.mentix.importers.sitereg] -ignore_sm_sites = true -{{< /highlight >}} -{{% /dir %}} diff --git a/docs/content/en/docs/config/http/services/siteacc/_index.md b/docs/content/en/docs/config/http/services/siteacc/_index.md index bf318f18fe..a556106c19 100644 --- a/docs/content/en/docs/config/http/services/siteacc/_index.md +++ b/docs/content/en/docs/config/http/services/siteacc/_index.md @@ -19,6 +19,15 @@ prefix = "/siteacc" {{< /highlight >}} {{% /dir %}} +## Security settings +{{% dir name="creds_passphrase" type="string" default="" %}} +The passphrase to use when encoding stored credentials. Should be exactly 32 characters long. +{{< highlight toml >}} +[http.services.siteacc.security] +creds_passphrase = "supersecretpasswordthatyouknow!" +{{< /highlight >}} +{{% /dir %}} + ## GOCDB settings {{% dir name="url" type="string" default="" %}} The external URL of the central GOCDB instance. @@ -111,12 +120,20 @@ driver = "file" {{< /highlight >}} {{% /dir %}} -### Storage settings - File driver -{{% dir name="file" type="string" default="" %}} -The file location. +### Storage settings - File drivers +{{% dir name="sites_file" type="string" default="" %}} +The sites file location. +{{< highlight toml >}} +[http.services.siteacc.storage.file] +sites_file = "/var/reva/sites.json" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="accounts_file" type="string" default="" %}} +The accounts file location. {{< highlight toml >}} [http.services.siteacc.storage.file] -file = "/var/reva/accounts.json" +accounts_file = "/var/reva/accounts.json" {{< /highlight >}} {{% /dir %}} diff --git a/docs/content/en/docs/config/packages/auth/manager/oidc/_index.md b/docs/content/en/docs/config/packages/auth/manager/oidc/_index.md index 6abc8039f1..758a8a14af 100644 --- a/docs/content/en/docs/config/packages/auth/manager/oidc/_index.md +++ b/docs/content/en/docs/config/packages/auth/manager/oidc/_index.md @@ -9,7 +9,7 @@ description: > # _struct: config_ {{% dir name="insecure" type="bool" default=false %}} -Whether to skip certificate checks when sending requests. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L55) +Whether to skip certificate checks when sending requests. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L61) {{< highlight toml >}} [auth.manager.oidc] insecure = false @@ -17,7 +17,7 @@ insecure = false {{% /dir %}} {{% dir name="issuer" type="string" default="" %}} -The issuer of the OIDC token. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L56) +The issuer of the OIDC token. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L62) {{< highlight toml >}} [auth.manager.oidc] issuer = "" @@ -25,7 +25,7 @@ issuer = "" {{% /dir %}} {{% dir name="id_claim" type="string" default="sub" %}} -The claim containing the ID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L57) +The claim containing the ID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L63) {{< highlight toml >}} [auth.manager.oidc] id_claim = "sub" @@ -33,7 +33,7 @@ id_claim = "sub" {{% /dir %}} {{% dir name="uid_claim" type="string" default="" %}} -The claim containing the UID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L58) +The claim containing the UID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L64) {{< highlight toml >}} [auth.manager.oidc] uid_claim = "" @@ -41,7 +41,7 @@ uid_claim = "" {{% /dir %}} {{% dir name="gid_claim" type="string" default="" %}} -The claim containing the GID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L59) +The claim containing the GID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L65) {{< highlight toml >}} [auth.manager.oidc] gid_claim = "" @@ -49,10 +49,26 @@ gid_claim = "" {{% /dir %}} {{% dir name="gatewaysvc" type="string" default="" %}} -The endpoint at which the GRPC gateway is exposed. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L60) +The endpoint at which the GRPC gateway is exposed. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L66) {{< highlight toml >}} [auth.manager.oidc] gatewaysvc = "" {{< /highlight >}} {{% /dir %}} +{{% dir name="users_mapping" type="string" default="" %}} + The optional OIDC users mapping file path [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L67) +{{< highlight toml >}} +[auth.manager.oidc] +users_mapping = "" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="group_claim" type="string" default="" %}} + The group claim to be looked up to map the user (default to 'groups'). [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidc/oidc.go#L68) +{{< highlight toml >}} +[auth.manager.oidc] +group_claim = "" +{{< /highlight >}} +{{% /dir %}} + diff --git a/docs/content/en/docs/config/packages/auth/manager/oidcmapping/_index.md b/docs/content/en/docs/config/packages/auth/manager/oidcmapping/_index.md index a7309eb3e1..8bc9d6f0f6 100644 --- a/docs/content/en/docs/config/packages/auth/manager/oidcmapping/_index.md +++ b/docs/content/en/docs/config/packages/auth/manager/oidcmapping/_index.md @@ -9,7 +9,7 @@ description: > # _struct: config_ {{% dir name="insecure" type="bool" default=false %}} -Whether to skip certificate checks when sending requests. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L57) +Whether to skip certificate checks when sending requests. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L59) {{< highlight toml >}} [auth.manager.oidcmapping] insecure = false @@ -17,7 +17,7 @@ insecure = false {{% /dir %}} {{% dir name="issuer" type="string" default="" %}} -The issuer of the OIDC token. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L58) +The issuer of the OIDC token. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L60) {{< highlight toml >}} [auth.manager.oidcmapping] issuer = "" @@ -25,7 +25,7 @@ issuer = "" {{% /dir %}} {{% dir name="id_claim" type="string" default="sub" %}} -The claim containing the ID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L59) +The claim containing the ID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L61) {{< highlight toml >}} [auth.manager.oidcmapping] id_claim = "sub" @@ -33,7 +33,7 @@ id_claim = "sub" {{% /dir %}} {{% dir name="uid_claim" type="string" default="" %}} -The claim containing the UID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L60) +The claim containing the UID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L62) {{< highlight toml >}} [auth.manager.oidcmapping] uid_claim = "" @@ -41,26 +41,34 @@ uid_claim = "" {{% /dir %}} {{% dir name="gid_claim" type="string" default="" %}} -The claim containing the GID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L61) +The claim containing the GID of the user. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L63) {{< highlight toml >}} [auth.manager.oidcmapping] gid_claim = "" {{< /highlight >}} {{% /dir %}} -{{% dir name="userprovidersvc" type="string" default="" %}} -The endpoint at which the GRPC userprovider is exposed. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L62) +{{% dir name="gatewaysvc" type="string" default="" %}} +The endpoint at which the GRPC gateway is exposed. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L64) {{< highlight toml >}} [auth.manager.oidcmapping] -userprovidersvc = "" +gatewaysvc = "" {{< /highlight >}} {{% /dir %}} -{{% dir name="usersmapping" type="string" default="" %}} - The OIDC users mapping file path [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L63) +{{% dir name="users_mapping" type="string" default="" %}} + The optional OIDC users mapping file path [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L65) {{< highlight toml >}} [auth.manager.oidcmapping] -usersmapping = "" +users_mapping = "" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="group_claim" type="string" default="" %}} + The group claim to be looked up to map the user (default to 'groups'). [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/auth/manager/oidcmapping/oidcmapping.go#L66) +{{< highlight toml >}} +[auth.manager.oidcmapping] +group_claim = "" {{< /highlight >}} {{% /dir %}} diff --git a/docs/package-lock.json b/docs/package-lock.json index 32afc31d9b..35305c603f 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -587,9 +587,9 @@ } }, "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "node-releases": { diff --git a/examples/datatx/datatx.toml b/examples/datatx/datatx.toml new file mode 100644 index 0000000000..cb50385005 --- /dev/null +++ b/examples/datatx/datatx.toml @@ -0,0 +1,22 @@ +# example data transfer service configuration +[grpc.services.datatx] +# rclone is the default data transfer driver +txdriver = "rclone" +# the shares,transfers db file (default: /var/tmp/reva/datatx-shares.json) +tx_shares_file = "" +# base folder of the data transfers (default: /home/DataTransfers) +data_transfers_folder = "" + +# rclone data transfer driver +[grpc.services.datatx.txdrivers.rclone] +# rclone endpoint +endpoint = "http://..." +# basic auth is used +auth_user = "...rcloneuser" +auth_pass = "...rcloneusersecret" +# the transfers(jobs) db file (default: /var/tmp/reva/datatx-transfers.json) +file = "" +# check status job interval in milliseconds +job_status_check_interval = 2000 +# the job timeout in milliseconds (must be long enough for big transfers!) +job_timeout = 120000 \ No newline at end of file diff --git a/examples/mentix/mentix.toml b/examples/mentix/mentix.toml index 1da40b784f..340ff4c508 100644 --- a/examples/mentix/mentix.toml +++ b/examples/mentix/mentix.toml @@ -8,10 +8,6 @@ update_interval = "15m" address = "http://sciencemesh-test.uni-muenster.de" apikey = "abc123" -# Sites can also be stored in a local file -[http.services.mentix.connectors.localfile] -file = "/usr/share/revad/sites.json" - # Configure the service types that are considered as critical/essential [http.services.mentix.services] critical_types = ["REVAD"] @@ -28,13 +24,6 @@ enabled_connectors = ["gocdb"] # Enable the Metrics exporter [http.services.mentix.exporters.metrics] -# Enable the site registration importer -[http.services.mentix.importers.sitereg] -# For importers, this is obligatory; the connectors will be used as the target for data updates -enabled_connectors = ["localfile"] -# If set to true, ScienceMesh sites will be ignored when they try to register -ignore_sm_sites = false - # Set up the accounts service used to query information about accounts associated with registered sites [http.services.mentix.accounts] # Depending on where the service is running, localhost may also be used here @@ -44,7 +33,6 @@ password = "userpass" # Configure the Prometheus Service Discovery: [http.services.mentix.exporters.promsd] -# The following files must be made available to Prometheus. -# They can then be used as the file_sd source of a job. -metrics_output_file = "/usr/share/prom/sciencemesh_metrics.json" -blackbox_output_file = "/usr/share/prom/sciencemesh_blackbox.json" +# The following path must be made available to Prometheus. +# The contained target files can then be used as the file_sd sources of a job. +output_path = "/usr/share/prom" diff --git a/examples/nextcloud-integration/custom-mime-types-demo.json b/examples/nextcloud-integration/custom-mime-types-demo.json new file mode 100644 index 0000000000..a48c986790 --- /dev/null +++ b/examples/nextcloud-integration/custom-mime-types-demo.json @@ -0,0 +1,4 @@ +{ + ".zmd": "application/compressed-markdown", + ".zep": "application/compressed-etherpad" +} diff --git a/examples/nextcloud-integration/revad.toml b/examples/nextcloud-integration/revad.toml index f30fbf2da8..329aa725ca 100644 --- a/examples/nextcloud-integration/revad.toml +++ b/examples/nextcloud-integration/revad.toml @@ -66,10 +66,13 @@ providers = "/etc/revad/providers.json" driver = "memory" [grpc.services.appprovider] -driver = "demo" -iopsecret = "testsecret" -wopiurl = "http://0.0.0.0:8880/" -wopibridgeurl = "http://localhost:8000/wopib" +driver = "wopi" + +[grpc.services.appprovider.drivers.wopi] +iop_secret = "hello" +wopi_url = "http://0.0.0.0:8880/" +app_name = "Collabora" +app_url = "https://your-collabora-server.org:9980" [grpc.services.appregistry] driver = "static" @@ -88,9 +91,7 @@ expose_data_server = true data_server_url = "http://127.0.0.1:19001/data" enable_home_creation = true disable_tus = true - -[grpc.services.storageprovider.mimetypes] -".zmd" = "application/compressed-markdown" +custom_mime_types_json = "custom-mime-types-demo.json" [grpc.services.storageprovider.drivers.nextcloud] endpoint = "http://localhost/apps/sciencemesh/" diff --git a/examples/ocmd/ocmd-server-1.toml b/examples/ocmd/ocmd-server-1.toml index f685a0e975..feef270e2d 100644 --- a/examples/ocmd/ocmd-server-1.toml +++ b/examples/ocmd/ocmd-server-1.toml @@ -28,9 +28,10 @@ ocmcoresvc = "localhost:19000" ocmshareprovidersvc = "localhost:19000" ocminvitemanagersvc = "localhost:19000" ocmproviderauthorizersvc = "localhost:19000" -commit_share_to_storage_grant = false datagateway = "http://localhost:19001/data" transfer_expires = 6 # give it a moment +commit_share_to_storage_grant = true +commit_share_to_storage_ref = true [grpc.services.authregistry] driver = "static" @@ -83,21 +84,12 @@ driver = "static" driver = "demo" app_provider_url = "localhost:19000" -[grpc.services.appprovider.drivers.wopi] -iop_secret = "hello" -wopi_url = "http://0.0.0.0:8880/" -app_name = "Collabora" -app_url = "https://your-collabora-server.org:9980" - [grpc.services.storageprovider] driver = "localhome" expose_data_server = true data_server_url = "http://localhost:19001/data" enable_home_creation = true -[grpc.services.storageprovider.mimetypes] -".zmd" = "application/compressed-markdown" - [grpc.services.storageprovider.drivers.localhome] user_layout = "{{.Username}}" diff --git a/examples/ocmd/ocmd-server-2.toml b/examples/ocmd/ocmd-server-2.toml index 02c1a972cc..5f0b6d701e 100644 --- a/examples/ocmd/ocmd-server-2.toml +++ b/examples/ocmd/ocmd-server-2.toml @@ -15,9 +15,10 @@ ocmcoresvc = "localhost:17000" ocmshareprovidersvc = "localhost:17000" ocminvitemanagersvc = "localhost:17000" ocmproviderauthorizersvc = "localhost:17000" -commit_share_to_storage_grant = false datagateway = "http://localhost:17001/data" transfer_expires = 6 # give it a moment +commit_share_to_storage_grant = true +commit_share_to_storage_ref = true [grpc.services.authregistry] driver = "static" diff --git a/examples/ocmd/users.demo.json b/examples/ocmd/users.demo.json index 0f96aeece1..e1b52cb806 100644 --- a/examples/ocmd/users.demo.json +++ b/examples/ocmd/users.demo.json @@ -2,7 +2,7 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "https://cernbox.cern.ch", + "idp": "cernbox.cern.ch", "type": 1 }, "username": "einstein", @@ -28,7 +28,7 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "https://cesnet.cz", + "idp": "cesnet.cz", "type": 1 }, "username": "marie", @@ -54,7 +54,7 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "https://example.org", + "idp": "example.org", "type": 1 }, "username": "richard", @@ -80,7 +80,7 @@ { "id": { "opaque_id": "932b4522-139b-4815-8ef4-42cdf82c3d51", - "idp": "https://example.com", + "idp": "example.com", "type": 1 }, "username": "test", diff --git a/examples/oidc-mapping-tpc/oidcmapping-1.toml b/examples/oidc-mapping-tpc/oidcmapping-1.toml new file mode 100644 index 0000000000..008416a9c2 --- /dev/null +++ b/examples/oidc-mapping-tpc/oidcmapping-1.toml @@ -0,0 +1,29 @@ +[shared] +jwt_secret = "Pive-Fumkiu4" + +# This toml config file will start a reva service that: +# - handles user metadata and user preferences +# - serves the grpc services on port 13000 +[grpc] +address = "0.0.0.0:13000" + +[grpc.services.authprovider] +auth_manager = "oidc" +[grpc.services.authprovider.auth_managers.json] +users = "users.json" +[grpc.services.authprovider.auth_managers.oidc] +gatewaysvc = "localhost:19000" +issuer = "https://iam-escape.cloud.cnaf.infn.it/" +# ESCAPE adopted the WLCG groups as group claims +group_claim = "wlcg.groups" +# The OIDC users mapping file path +users_mapping = "users-oidc-1.demo.json" +# If your local identity provider service configuration includes further claims, +# please configure them also here +#uid_claim = "" +#gid_claim = "" + +[grpc.services.userprovider] +driver = "json" +[grpc.services.userprovider.drivers.json] +users = "users.demo.json" diff --git a/examples/oidc-mapping-tpc/oidcmapping-2.toml b/examples/oidc-mapping-tpc/oidcmapping-2.toml new file mode 100644 index 0000000000..d9bf633f36 --- /dev/null +++ b/examples/oidc-mapping-tpc/oidcmapping-2.toml @@ -0,0 +1,29 @@ +[shared] +jwt_secret = "Pive-Fumkiu4" + +# This toml config file will start a reva service that: +# - handles user metadata and user preferences +# - serves the grpc services on port 14000 +[grpc] +address = "0.0.0.0:14000" + +[grpc.services.authprovider] +auth_manager = "oidc" +[grpc.services.authprovider.auth_managers.json] +users = "users.json" +[grpc.services.authprovider.auth_managers.oidc] +gatewaysvc = "localhost:17000" +issuer = "https://iam-escape.cloud.cnaf.infn.it/" +# ESCAPE adopted the WLCG groups as group claims +group_claim = "wlcg.groups" +# The OIDC users mapping file path +users_mapping = "users-oidc-2.demo.json" +# If your local identity provider service configuration includes further claims, +# please configure them also here +#uid_claim = "" +#gid_claim = "" + +[grpc.services.userprovider] +driver = "json" +[grpc.services.userprovider.drivers.json] +users = "users.demo.json" diff --git a/examples/oidc-mapping-tpc/providers.demo.json b/examples/oidc-mapping-tpc/providers.demo.json new file mode 100644 index 0000000000..05aa6c78d3 --- /dev/null +++ b/examples/oidc-mapping-tpc/providers.demo.json @@ -0,0 +1,198 @@ +[ + { + "name": "cernbox", + "full_name": "CERNBox", + "organization": "CERN", + "domain": "cernbox.cern.ch", + "homepage": "https://cernbox.web.cern.ch", + "description": "CERNBox provides cloud data storage to all CERN users.", + "services": [ + { + "endpoint": { + "type": { + "name": "OCM", + "description": "CERNBox Open Cloud Mesh API" + }, + "name": "CERNBox - OCM API", + "path": "http://127.0.0.1:19001/ocm/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Webdav", + "description": "CERNBox Webdav API" + }, + "name": "CERNBox - Webdav API", + "path": "http://127.0.0.1:19001/remote.php/webdav/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "CERNBox GRPC Gateway" + }, + "name": "CERNBox - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" + } + ] + }, + { + "name": "oc-cesnet", + "full_name": "ownCloud@CESNET", + "organization": "CESNET", + "domain": "cesnet.cz", + "homepage": "https://owncloud.cesnet.cz", + "description": "OwnCloud has been designed for individual users.", + "services": [ + { + "endpoint": { + "type": { + "name": "OCM", + "description": "CESNET Open Cloud Mesh API" + }, + "name": "CESNET - OCM API", + "path": "http://127.0.0.1:17001/ocm/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:17001/" + }, + { + "endpoint": { + "type": { + "name": "Webdav", + "description": "CESNET Webdav API" + }, + "name": "CESNET - Webdav API", + "path": "http://127.0.0.1:17001/remote.php/webdav/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:17001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "CESNET GRPC Gateway" + }, + "name": "CESNET - GRPC Gateway", + "path": "127.0.0.1:17000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:17000" + } + ] + }, + { + "name": "example", + "full_name": "ownCloud@Example", + "organization": "Example", + "domain": "example.org", + "homepage": "http://example.org", + "description": "Example cloud storage.", + "services": [ + { + "endpoint": { + "type": { + "name": "OCM", + "description": "Example Open Cloud Mesh API" + }, + "name": "Example - OCM API", + "path": "http://127.0.0.1:19001/ocm/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Webdav", + "description": "Example Webdav API" + }, + "name": "Example - Webdav API", + "path": "http://127.0.0.1:19001/remote.php/webdav/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "Example GRPC Gateway" + }, + "name": "Example - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" + } + ] + }, + { + "name": "test", + "full_name": "ownCloud@Test", + "organization": "Test", + "domain": "test.org", + "homepage": "http://test.org", + "description": "Test cloud storage.", + "services": [ + { + "endpoint": { + "type": { + "name": "OCM", + "description": "Test Open Cloud Mesh API" + }, + "name": "Test - OCM API", + "path": "http://127.0.0.1:19001/ocm/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Webdav", + "description": "Test Webdav API" + }, + "name": "Test - Webdav API", + "path": "http://127.0.0.1:19001/remote.php/webdav/", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "Test GRPC Gateway" + }, + "name": "Test - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" + } + ] + } +] diff --git a/examples/oidc-mapping-tpc/server-1.toml b/examples/oidc-mapping-tpc/server-1.toml new file mode 100644 index 0000000000..c212892e59 --- /dev/null +++ b/examples/oidc-mapping-tpc/server-1.toml @@ -0,0 +1,67 @@ +[grpc] +address = "0.0.0.0:19000" + +[shared] +jwt_secret = "jwt_secret" +gatewaysvc = "localhost:19000" + +# services to enable +[grpc.services.gateway] +commit_share_to_storage_grant = true +commit_share_to_storage_ref = true + +[grpc.services.authprovider] +[grpc.services.authprovider.auth_managers.json] +users = "users.demo.json" + +[grpc.services.userprovider.drivers.json] +users = "users.demo.json" + +[grpc.services.authregistry] +[grpc.services.authregistry.drivers.static.rules] +bearer = "localhost:13000" + +[grpc.services.storageregistry] +[grpc.services.storageregistry.drivers.static] +home_provider = "/home" + +[grpc.services.storageregistry.drivers.static.rules] +"/home" = {"address" = "localhost:19000"} +"123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:19000"} + +[grpc.services.storageprovider] +driver = "localhome" +mount_path = "/home" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +expose_data_server = true +data_server_url = "http://localhost:19001/data" +enable_home_creation = true + +[grpc.services.usershareprovider] +[grpc.services.groupprovider] +[grpc.services.publicshareprovider] +[grpc.services.ocmcore] + +[grpc.services.ocmshareprovider] +gateway_addr = "0.0.0.0:19000" + +[grpc.services.ocminvitemanager] +[grpc.services.ocmproviderauthorizer] +[grpc.services.ocmproviderauthorizer.drivers.json] +providers = "providers.demo.json" + +[http.middlewares.providerauthorizer.drivers.json] +providers = "providers.demo.json" + +[http] +address = "0.0.0.0:19001" + +[http.services.dataprovider] +driver = "localhome" + +[http.services.datagateway] +[http.services.prometheus] +[http.services.ocmd] +[http.services.ocs] +[http.services.ocdav] +enable_http_tpc = true diff --git a/examples/oidc-mapping-tpc/server-2.toml b/examples/oidc-mapping-tpc/server-2.toml new file mode 100644 index 0000000000..259c4b77d8 --- /dev/null +++ b/examples/oidc-mapping-tpc/server-2.toml @@ -0,0 +1,67 @@ +[grpc] +address = "0.0.0.0:17000" + +[shared] +jwt_secret = "jwt_secret" +gatewaysvc = "localhost:17000" + +# services to enable +[grpc.services.gateway] +commit_share_to_storage_grant = true +commit_share_to_storage_ref = true + +[grpc.services.authprovider] +[grpc.services.authprovider.auth_managers.json] +users = "users.demo.json" + +[grpc.services.userprovider.drivers.json] +users = "users.demo.json" + +[grpc.services.authregistry] +[grpc.services.authregistry.drivers.static.rules] +bearer = "localhost:14000" + +[grpc.services.storageregistry] +[grpc.services.storageregistry.drivers.static] +home_provider = "/home" + +[grpc.services.storageregistry.drivers.static.rules] +"/home" = {"address" = "localhost:17000"} +"123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:17000"} + +[grpc.services.storageprovider] +driver = "localhome" +mount_path = "/home" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +expose_data_server = true +data_server_url = "http://localhost:17001/data" +enable_home_creation = true + +[grpc.services.usershareprovider] +[grpc.services.groupprovider] +[grpc.services.publicshareprovider] +[grpc.services.ocmcore] + +[grpc.services.ocmshareprovider] +gateway_addr = "0.0.0.0:17000" + +[grpc.services.ocminvitemanager] +[grpc.services.ocmproviderauthorizer] +[grpc.services.ocmproviderauthorizer.drivers.json] +providers = "providers.demo.json" + +[http.middlewares.providerauthorizer.drivers.json] +providers = "providers.demo.json" + +[http] +address = "0.0.0.0:17001" + +[http.services.dataprovider] +driver = "localhome" + +[http.services.datagateway] +[http.services.prometheus] +[http.services.ocmd] +[http.services.ocs] +[http.services.ocdav] +enable_http_tpc = true diff --git a/examples/oidc-mapping/users-oidcmapping.json b/examples/oidc-mapping-tpc/users-oidcmapping-1.demo.json similarity index 62% rename from examples/oidc-mapping/users-oidcmapping.json rename to examples/oidc-mapping-tpc/users-oidcmapping-1.demo.json index 8678ed60b0..2eed64a504 100644 --- a/examples/oidc-mapping/users-oidcmapping.json +++ b/examples/oidc-mapping-tpc/users-oidcmapping-1.demo.json @@ -5,8 +5,8 @@ "username": "einstein" }, { - "oidc_issuer": "http://iam-login-service:8080/", - "oidc_group": "Sciencemesh", + "oidc_issuer": "https://iam-escape.cloud.cnaf.infn.it/", + "oidc_group": "/escape", "username": "marie" } ] diff --git a/examples/oidc-mapping-tpc/users-oidcmapping-2.demo.json b/examples/oidc-mapping-tpc/users-oidcmapping-2.demo.json new file mode 100644 index 0000000000..0742a2b1cd --- /dev/null +++ b/examples/oidc-mapping-tpc/users-oidcmapping-2.demo.json @@ -0,0 +1,7 @@ +[ + { + "oidc_issuer": "https://iam-escape.cloud.cnaf.infn.it/", + "oidc_group": "/escape", + "username": "einstein" + } +] diff --git a/examples/oidc-mapping/users.json b/examples/oidc-mapping-tpc/users.demo.json similarity index 64% rename from examples/oidc-mapping/users.json rename to examples/oidc-mapping-tpc/users.demo.json index 342e54b900..38932b65a0 100644 --- a/examples/oidc-mapping/users.json +++ b/examples/oidc-mapping-tpc/users.demo.json @@ -2,7 +2,8 @@ { "id": { "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", - "idp": "reva-oidc-escape:20080" + "idp": "http://localhost:20080", + "type": 1 }, "username": "einstein", "secret": "relativity", @@ -13,7 +14,8 @@ { "id": { "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - "idp": "reva-oidc-escape:20080" + "idp": "http://localhost:20080", + "type": 1 }, "username": "marie", "secret": "radioactivity", @@ -24,7 +26,8 @@ { "id": { "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", - "idp": "reva-oidc-escape:20080" + "idp": "http://localhost:20080", + "type": 1 }, "username": "richard", "secret": "superfluidity", @@ -34,13 +37,14 @@ }, { "id": { - "opaque_id": "4029579c-6ad5-4cec-a9ce-e843f77de452", - "idp": "reva-oidc-escape:20080" + "opaque_id": "0e4d9dc1-8349-49fe-8afc-6b844aec1cf6", + "idp": "http://localhost:20080", + "type": 7 }, - "username": "jimmie", - "secret": "spokenword", - "mail": "jimmie@surfsara.nl", - "display_name": "Jimmie Rigg", - "groups": ["sailing-lovers", "violin-haters", "physics-lovers"] + "username": "lwaccount", + "secret": "lightweight", + "mail": "lwaccount@example.org", + "display_name": "Lightweight Test Account", + "groups": ["guest-users", "weight-loss-club", "physics-lovers"] } ] diff --git a/examples/oidc-mapping/gateway.toml b/examples/oidc-mapping/gateway.toml deleted file mode 100644 index 7e43c757de..0000000000 --- a/examples/oidc-mapping/gateway.toml +++ /dev/null @@ -1,39 +0,0 @@ -[shared] -jwt_secret = "jwt_secret" - -# services to enable -[grpc.services.gateway] -commit_share_to_storage_grant = true -commit_share_to_storage_ref = true - -[grpc.services.storageregistry] -[grpc.services.storageregistry.drivers.static] -home_provider = "/home" - -[grpc.services.authregistry.drivers.static.rules] -oidcmapping = "localhost:13000" - -[grpc.services.storageregistry.drivers.static.rules."/home"] -address = "localhost:17000" -[grpc.services.storageregistry.drivers.static.rules."/reva"] -address = "localhost:18000" -[grpc.services.storageregistry.drivers.static.rules."123e4567-e89b-12d3-a456-426655440000"] -address = "localhost:18000" - -[grpc.services.authregistry] -[grpc.services.usershareprovider] -[grpc.services.groupprovider] -[grpc.services.publicshareprovider] -[grpc.services.ocmcore] - -[grpc.services.ocmshareprovider] -gateway_addr = "0.0.0.0:19000" - -[grpc.services.ocminvitemanager] -[grpc.services.ocmproviderauthorizer] - -[http.services.datagateway] -[http.services.prometheus] -[http.services.ocmd] -[http.services.ocdav] -[http.services.ocs] diff --git a/examples/oidc-mapping/storage-home.toml b/examples/oidc-mapping/storage-home.toml deleted file mode 100644 index 0323c24455..0000000000 --- a/examples/oidc-mapping/storage-home.toml +++ /dev/null @@ -1,17 +0,0 @@ -[grpc] -address = "0.0.0.0:17000" - -[grpc.services.storageprovider] -driver = "localhome" -mount_path = "/home" -mount_id = "123e4567-e89b-12d3-a456-426655440000" -data_server_url = "http://localhost:17001/data" - -[grpc.services.storageprovider.drivers.localhome] -shadow = "shadowfolder" - -[http] -address = "0.0.0.0:17001" - -[http.services.dataprovider] -driver = "localhome" diff --git a/examples/oidc-mapping/users-oidcmapping.toml b/examples/oidc-mapping/users-oidcmapping.toml deleted file mode 100644 index 54bba68ac3..0000000000 --- a/examples/oidc-mapping/users-oidcmapping.toml +++ /dev/null @@ -1,23 +0,0 @@ -[shared] -jwt_secret = "Pive-Fumkiu4" - -# This toml config file will start a reva service that: -# - handles user metadata and user preferences -# - serves the grpc services on port 13000 -[grpc] -address = "0.0.0.0:13000" - -[grpc.services.authprovider] -auth_manager = "oidcmapping" -[grpc.services.authprovider.auth_managers.json] -users = "users.json" -[grpc.services.authprovider.auth_managers.oidcmapping] -issuer = "http://iam-login-service:8080/" -userprovidersvc = "0.0.0.0:13000" -# The OIDC users mapping file path -usersmapping = "/go/src/github/cs3org/reva/examples/oidc-mapping/users-oidcmapping.json" - -[grpc.services.userprovider] -driver = "json" -[grpc.services.userprovider.drivers.json] -users = "users.json" diff --git a/examples/plugin/json/json.go b/examples/plugin/json/json.go index c56aec065c..569b9e2498 100644 --- a/examples/plugin/json/json.go +++ b/examples/plugin/json/json.go @@ -81,20 +81,28 @@ func (m *Manager) Configure(ml map[string]interface{}) error { } // GetUser returns the user based on the uid. -func (m *Manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *Manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { for _, u := range m.users { if (u.Id.GetOpaqueId() == uid.OpaqueId || u.Username == uid.OpaqueId) && (uid.Idp == "" || uid.Idp == u.Id.GetIdp()) { - return u, nil + user := *u + if skipFetchingGroups { + user.Groups = nil + } + return &user, nil } } return nil, nil } // GetUserByClaim returns user based on the claim -func (m *Manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *Manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { for _, u := range m.users { if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim { - return u, nil + user := *u + if skipFetchingGroups { + user.Groups = nil + } + return &user, nil } } return nil, errtypes.NotFound(value) @@ -126,11 +134,15 @@ func userContains(u *userpb.User, query string) bool { } // FindUsers returns the user based on the query -func (m *Manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *Manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { users := []*userpb.User{} for _, u := range m.users { if userContains(u, query) { - users = append(users, u) + user := *u + if skipFetchingGroups { + user.Groups = nil + } + users = append(users, &user) } } return users, nil @@ -138,7 +150,7 @@ func (m *Manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, // GetUserGroups returns the user groups func (m *Manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { - user, err := m.GetUser(ctx, uid) + user, err := m.GetUser(ctx, uid, false) if err != nil { return nil, err } diff --git a/examples/siteacc/siteacc.toml b/examples/siteacc/siteacc.toml index cf081e748e..68a0c52e4f 100644 --- a/examples/siteacc/siteacc.toml +++ b/examples/siteacc/siteacc.toml @@ -1,6 +1,11 @@ [http] address = "0.0.0.0:9600" +# Security settings +[http.services.siteacc.security] +creds_passphrase = "superdupersecret" + +# Connection to the GOCDB [http.services.siteacc.gocdb] url = "https://sciencemesh-test.uni-muenster.de/gocdb/" write_url = "https://sciencemesh-test.uni-muenster.de/gocdbpi/" @@ -10,7 +15,8 @@ apikey = "verysecret" [http.services.siteacc.storage] driver = "file" [http.services.siteacc.storage.file] -file = "/var/revad/accounts.json" +sites_file = "/var/revad/sites.json" +accounts_file = "/var/revad/accounts.json" # Email related settings [http.services.siteacc.email] diff --git a/go.mod b/go.mod index b5a655ddf0..cd5ed3daff 100644 --- a/go.mod +++ b/go.mod @@ -2,34 +2,29 @@ module github.com/cs3org/reva/v2 require ( bou.ke/monkey v1.0.2 - contrib.go.opencensus.io/exporter/prometheus v0.4.0 - github.com/BurntSushi/toml v1.0.0 - github.com/CiscoM31/godata v1.0.5 + contrib.go.opencensus.io/exporter/prometheus v0.4.1 + github.com/BurntSushi/toml v1.1.0 github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/ReneKroon/ttlcache/v2 v2.11.0 - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.42.39 + github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75 + github.com/aws/aws-sdk-go v1.43.28 github.com/beevik/etree v1.1.0 github.com/bluele/gcache v0.0.2 github.com/c-bata/go-prompt v0.2.5 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/ceph/go-ceph v0.13.0 + github.com/ceph/go-ceph v0.14.0 github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde + github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/dgraph-io/ristretto v0.1.0 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 github.com/go-chi/chi/v5 v5.0.7 - github.com/go-ldap/ldap/v3 v3.4.1 - github.com/go-micro/plugins/v4/events/natsjs v1.0.0 - github.com/go-micro/plugins/v4/server/http v1.0.0 - github.com/go-openapi/errors v0.20.1 // indirect - github.com/go-openapi/strfmt v0.20.2 // indirect + github.com/go-ldap/ldap/v3 v3.4.2 github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -40,53 +35,51 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 - github.com/hashicorp/go-hclog v1.1.0 + github.com/hashicorp/go-hclog v1.2.0 github.com/hashicorp/go-plugin v1.4.3 github.com/huandu/xstrings v1.3.2 // indirect - github.com/iancoleman/strcase v0.2.0 github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/juliangruber/go-intersect v1.1.0 github.com/mattn/go-sqlite3 v1.14.10 github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b github.com/mileusna/useragent v1.0.2 - github.com/minio/minio-go/v7 v7.0.21 + github.com/minio/minio-go/v7 v7.0.24 github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 github.com/nats-io/nats-server/v2 v2.7.4 - github.com/onsi/ginkgo/v2 v2.0.0 - github.com/onsi/gomega v1.18.1 + github.com/nats-io/nats-streaming-server v0.24.3 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.19.0 github.com/pkg/errors v0.9.1 - github.com/pkg/xattr v0.4.4 + github.com/pkg/xattr v0.4.5 github.com/pquerna/cachecontrol v0.1.0 // indirect - github.com/prometheus/alertmanager v0.23.0 - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/alertmanager v0.24.0 github.com/rs/cors v1.8.2 github.com/rs/zerolog v1.26.1 github.com/sciencemesh/meshdirectory-web v1.0.4 github.com/sethvargo/go-password v0.2.0 - github.com/stretchr/testify v1.7.0 - github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f + github.com/stretchr/testify v1.7.1 + github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df github.com/thanhpk/randstr v1.0.4 github.com/tidwall/pretty v1.2.0 // indirect github.com/tus/tusd v1.8.0 github.com/wk8/go-ordered-map v0.2.0 - go-micro.dev/v4 v4.6.0 - go.mongodb.org/mongo-driver v1.7.2 // indirect + go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908 go.opencensus.io v0.23.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 - go.opentelemetry.io/otel v1.3.0 - go.opentelemetry.io/otel/exporters/jaeger v1.3.0 - go.opentelemetry.io/otel/sdk v1.3.0 - go.opentelemetry.io/otel/trace v1.3.0 - golang.org/x/crypto v0.0.0-20220214200702-86341886e292 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.31.0 + go.opentelemetry.io/otel v1.6.1 + go.opentelemetry.io/otel/exporters/jaeger v1.6.1 + go.opentelemetry.io/otel/sdk v1.6.1 + go.opentelemetry.io/otel/trace v1.6.1 + golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 + golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 google.golang.org/genproto v0.0.0-20211021150943-2b146023228c - google.golang.org/grpc v1.42.0 - google.golang.org/protobuf v1.27.1 + google.golang.org/grpc v1.45.0 + google.golang.org/protobuf v1.28.0 gopkg.in/square/go-jose.v2 v2.6.0 // indirect gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index ccf6661f30..1268b2e9c0 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= -contrib.go.opencensus.io/exporter/prometheus v0.4.0 h1:0QfIkj9z/iVZgK31D9H9ohjjIDApI2GOPScCKwxedbs= -contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= +contrib.go.opencensus.io/exporter/prometheus v0.4.1 h1:oObVeKo2NxpdF/fIfrPsNj6K0Prg0R0mHM+uANlYMiM= +contrib.go.opencensus.io/exporter/prometheus v0.4.1/go.mod h1:t9wvfitlUjGXG2IXAZsuFq26mDGid/JwCEXp+gTG/9U= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -79,11 +79,10 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CiscoM31/godata v1.0.5 h1:AITXpa/5ybXEq59A0nqUGiS7ZXVJnQtFw5o09tyN/UA= -github.com/CiscoM31/godata v1.0.5/go.mod h1:wcmFm66GMdOE316TgwFO1wo5ainCvTK26omd93oZf2M= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -94,23 +93,18 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -118,35 +112,31 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a h1:6tD4saJb8wmYF6Llz96ZJwUQ5r2GyTBFA2VEB5z8gVY= github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a/go.mod h1:XYuK1S5+kS6FGhlIUFuZFPvWiSrOIoLk6+ro33Xce3Y= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75 h1:G5Degn+tmIBpRCD7vPVpCoAol2gd/S7s00z5CWpzp5U= +github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75/go.mod h1:BxrcQ4TPPMevB2udKEAHenQxCUh1xXVItoU2CbvVdcQ= github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.37.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.42.39 h1:6Lso73VoCI8Zmv3zAMv4BNg2gHAKNOlbLv1s/ew90SI= -github.com/aws/aws-sdk-go v1.42.39/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.28 h1:HrBUf2pYEMRB3GDkSa/bZ2lkZIe8gSUOz/IEupG1Te0= +github.com/aws/aws-sdk-go v1.43.28/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -154,7 +144,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= @@ -169,21 +158,24 @@ github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7F github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/ceph/go-ceph v0.13.0 h1:69dgIPlNHD2OCz98T0benI4++vcnShGcpQK4RIALjw4= -github.com/ceph/go-ceph v0.13.0/go.mod h1:mafFpf5Vg8Ai8Bd+FAMvKBHLmtdpTXdRP/TNq8XWegY= +github.com/ceph/go-ceph v0.14.0 h1:sJoT0au7NT3TPmDWf5W9w6tZy0U/5xZrIXVVauZR+Xo= +github.com/ceph/go-ceph v0.14.0/go.mod h1:mafFpf5Vg8Ai8Bd+FAMvKBHLmtdpTXdRP/TNq8XWegY= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= @@ -206,14 +198,13 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde h1:WrD9O8ZaWvsm0eBzpzVBIuczDhqVq50Nmjc7PGHHA9Y= -github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9 h1:SuPu5Mc2mpz+J059XML+cMd0i5FZR4t/kROS3SaIsnU= +github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -232,7 +223,6 @@ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyG github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -240,15 +230,16 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exoscale/egoscale v0.46.0/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -262,12 +253,7 @@ github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v github.com/gdexlab/go-render v1.0.1/go.mod h1:wRi5nW2qfjiGj4mPukH4UV0IknS1cHD4VgFTmJX5JzM= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-acme/lego/v4 v4.4.0/go.mod h1:l3+tFUFZb590dWcqhWZegynUthtaHJbG2fevUpoOOE0= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= @@ -277,132 +263,60 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU= -github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8= +github.com/go-ldap/ldap/v3 v3.4.2/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= -github.com/go-micro/plugins/v4/events/natsjs v1.0.0 h1:vp2BNA1i6a1mj/jY1GZ+l/JnLWi6lsGPnYi0CtccQvU= -github.com/go-micro/plugins/v4/events/natsjs v1.0.0/go.mod h1:pb7sf8Ych7t7eCuw7jj6ac6rhUrga8SEA55YEFiwmrY= -github.com/go-micro/plugins/v4/server/http v1.0.0 h1:cuSp4F8uS7sWjvmsJ8M4k18rZF3AxA2qdLA6kFxPktM= -github.com/go-micro/plugins/v4/server/http v1.0.0/go.mod h1:E8eoUONK91jcMpvkcFUsqbgDyKyri3x7ty2GuM5SsI0= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= -github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= -github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.0/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8= -github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= -github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= -github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= -github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= -github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= -github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= -github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= -github.com/go-openapi/runtime v0.19.29/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= -github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= -github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= -github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= -github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= -github.com/go-openapi/strfmt v0.20.1/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= -github.com/go-openapi/strfmt v0.20.2 h1:6XZL+fF4VZYFxKQGLAUB358hOrRh/wS51uWEtlONADE= -github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= +github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os= +github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= -github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= -github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= -github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= -github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= -github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= -github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -432,17 +346,17 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -533,7 +447,6 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -564,16 +477,25 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -581,15 +503,21 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.3.6 h1:v5xW5KzByoerQlN/o31VJrFNiozgzGyDoMgDJgXpsto= +github.com/hashicorp/raft v1.3.6/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -606,7 +534,6 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= @@ -627,8 +554,9 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -641,14 +569,14 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4= github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -665,7 +593,6 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -674,7 +601,10 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HK github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= @@ -682,31 +612,31 @@ github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wd github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -724,17 +654,17 @@ github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= -github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.21 h1:xrc4BQr1Fa4s5RwY0xfMjPZFJ1bcYBCCHYlngBdWV+k= -github.com/minio/minio-go/v7 v7.0.21/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do= +github.com/minio/minio-go/v7 v7.0.24 h1:HPlHiET6L5gIgrHRaw1xFo1OaN4bEP/082asWh3WJtI= +github.com/minio/minio-go/v7 v7.0.24/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -754,9 +684,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -769,23 +697,36 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= +github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY= github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.6.2/go.mod h1:CNi6dJQ5H+vWqaoWKjCGtqBt7ai/xOTLiocUqhK6ews= +github.com/nats-io/nats-server/v2 v2.6.4/go.mod h1:LlMieumxNUnCloOTVFv7Wog0YnasScxARUMXVXv9/+M= github.com/nats-io/nats-server/v2 v2.7.4 h1:c+BZJ3rGzUKCBIM4IXO8uNT2u1vajGbD1kPA6wqCEaM= github.com/nats-io/nats-server/v2 v2.7.4/go.mod h1:1vZ2Nijh8tcyNe8BDVyTviCd9NYzRbubQYiEHsvOQWc= +github.com/nats-io/nats-streaming-server v0.23.0/go.mod h1:1asNNRpUKbgwoPqRLEWbJE65uqmWjG1YN/Xlo3WgkTY= +github.com/nats-io/nats-streaming-server v0.24.3 h1:uZez8jBkXscua++jaDsK7DhpSAkizdetar6yWbPMRco= +github.com/nats-io/nats-streaming-server v0.24.3/go.mod h1:rqWfyCbxlhKj//fAp8POdQzeADwqkVhZcoWlbhkuU5w= +github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d h1:zJf4l8Kp67RIZhoVeniSLZs69SHNgjLHz0aNsqPPlx8= github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nats-io/stan.go v0.10.0/go.mod h1:0jEuBXKauB1HHJswHM/lx05K48TJ1Yxj6VIfM4k+aB4= +github.com/nats-io/stan.go v0.10.2 h1:gQLd05LhzmhFkHm3/qP/klYHfM/hys45GyHa1Uly/kI= +github.com/nats-io/stan.go v0.10.2/go.mod h1:vo2ax8K2IxaR3JtEMLZRFKIdoK/3o1/PKueapB7ezX0= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= @@ -808,14 +749,16 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -830,11 +773,11 @@ github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTX github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -845,24 +788,27 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= -github.com/pkg/xattr v0.4.4 h1:FSoblPdYobYoKCItkqASqcrKCxRn9Bgurz0sCBwzO5g= -github.com/pkg/xattr v0.4.4/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs= +github.com/pkg/xattr v0.4.5 h1:P5SvUc1T07cHLto76ESJ+/x5kexU7s9127iVoeEW/hs= +github.com/pkg/xattr v0.4.5/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prometheus/alertmanager v0.23.0 h1:KIb9IChC3kg+1CC388qfr7bsT+tARpQqdsCMoatdObA= -github.com/prometheus/alertmanager v0.23.0/go.mod h1:0MLTrjQI8EuVmvykEhcfr/7X0xmaDAZrqMgxIq3OXHk= +github.com/prometheus/alertmanager v0.24.0 h1:HBWR3lk4uy3ys+naDZthDdV7yEsxpaNeZuUS+hJgrOw= +github.com/prometheus/alertmanager v0.24.0/go.mod h1:r6fy/D7FRuZh5YbnX6J3MBY0eI4Pb5yPYS7/bPSXXqI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -870,6 +816,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -878,12 +825,13 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= +github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -901,7 +849,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -909,7 +856,6 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -918,15 +864,12 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/ github.com/sciencemesh/meshdirectory-web v1.0.4 h1:1YSctF6PAXhoHUYCaeRTj7rHaF7b3rYrZf2R0VXBIbo= github.com/sciencemesh/meshdirectory-web v1.0.4/go.mod h1:fJSThTS3xf+sTdL0iXQoaQJssLI7tn7DetHMHUl4SRk= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= @@ -965,7 +908,6 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -973,10 +915,11 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= -github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8gRPt1MdSzBNZP0OYuDm5wsmDKgwpLjYzo= +github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= @@ -989,33 +932,26 @@ github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4 github.com/tus/tusd v1.1.0/go.mod h1:3DWPOdeCnjBwKtv98y5dSws3itPqfce5TVa0s59LRiA= github.com/tus/tusd v1.8.0 h1:QODQ5uMhL2tFX3Ouk7rUHHqPqeDBvi2+gYIoyUO0n8Q= github.com/tus/tusd v1.8.0/go.mod h1:stZzKpol4qz7lX2HXy/1H526dn5mRnkIICTW2lrh9NM= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs= github.com/vimeo/go-util v1.4.1/go.mod h1:r+yspV//C48HeMXV8nEvtUeNiIiGfVv3bbEHzOgudwE= github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs= github.com/wk8/go-ordered-map v0.2.0 h1:KlvGyHstD1kkGZkPtHCyCfRYS0cz84uk6rrW/Dnhdtk= github.com/wk8/go-ordered-map v0.2.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= @@ -1028,19 +964,17 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go-micro.dev/v4 v4.6.0 h1:sY1Ps3Vgq8tFzcUGps9WnJhy1AKspXK+4wWIwugiRss= -go-micro.dev/v4 v4.6.0/go.mod h1:7UY87mLE6T4zHKsNS5D+VWZcXGTEvU1rbA90PezzlWM= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908 h1:4ori3xawGl2unFIOQPEgUuHdlrvihc+dsIot7XUzc2k= +go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908/go.mod h1:tw47Xfg2YywfPUnglZgXQsSf7p0ST6mQL3v0JooGmSY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= -go.mongodb.org/mongo-driver v1.7.2 h1:pFttQyIiJUHEn50YfZgC9ECjITMT44oiN36uArf/OFg= -go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.m3o.com v0.1.0/go.mod h1:p8FdLqZH3R9a0y04qiMNT+clw69d3SxyQPFzCNbDRtk= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1051,16 +985,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 h1:Ky1MObd188aGbgb5OgNnwGuEEwI9MVIcc7rBW6zk5Ak= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= -go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0= -go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc= -go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.31.0 h1:li8u9OSMvLau7rMs8bmiL82OazG6MAkwPz2i6eS8TBQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.31.0/go.mod h1:SY9qHHUES6W3oZnO1H2W8NvsSovIoXRg/A1AH9px8+I= +go.opentelemetry.io/otel v1.6.1 h1:6r1YrcTenBvYa1x491d0GGpTVBsNECmrc/K6b+zDeis= +go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ= +go.opentelemetry.io/otel/exporters/jaeger v1.6.1 h1:7xuwXr3qUWq48Chyuq+VvomV3KjXZLd5seQwg83s/sU= +go.opentelemetry.io/otel/exporters/jaeger v1.6.1/go.mod h1:Cu6mKJ+LLTPuOBX830xM4wVKIsVpHSXa50uN7aAxraQ= +go.opentelemetry.io/otel/sdk v1.6.1 h1:ZmcNyMhcuAYIb/Nr6QhBPTMopMTbov/47wHt1gibkoY= +go.opentelemetry.io/otel/sdk v1.6.1/go.mod h1:IVYrddmFZ+eJqu2k38qD3WezFR2pymCzm8tdxyh3R4E= +go.opentelemetry.io/otel/trace v1.6.1 h1:f8c93l5tboBYZna1nWk0W9DYyMzJXDWdZcJZ0Kb400U= +go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1074,14 +1008,10 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -1099,10 +1029,11 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1141,12 +1072,11 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1155,7 +1085,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1164,7 +1093,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1180,33 +1108,31 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1249,7 +1175,6 @@ golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190415081028-16da32be82c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1259,7 +1184,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1303,6 +1227,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1320,6 +1245,7 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1335,11 +1261,15 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= +golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1358,17 +1288,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1378,13 +1307,12 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1435,9 +1363,9 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1566,8 +1494,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxlpi0JI4Umw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1578,8 +1507,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= @@ -1594,9 +1521,9 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/telebot.v3 v3.0.0/go.mod h1:7rExV8/0mDDNu9epSrDm/8j22KLaActH1Tbee6YjzWg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index a78bd37016..c090289b6d 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -43,6 +43,7 @@ import ( ) var userGroupsCache gcache.Cache +var scopeExpansionCache gcache.Cache type config struct { // TODO(labkode): access a map is more performant as uri as fixed in length @@ -76,6 +77,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI conf.GatewayAddr = sharedconf.GetGatewaySVC(conf.GatewayAddr) userGroupsCache = gcache.New(1000000).LFU().Build() + scopeExpansionCache = gcache.New(1000000).LFU().Build() h, ok := tokenmgr.NewFuncs[conf.TokenManager] if !ok { @@ -97,7 +99,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI // to decide the storage provider. tkn, ok := ctxpkg.ContextGetToken(ctx) if ok { - u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, false) + u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, true) if err == nil { // store user and scopes in context ctx = ctxpkg.ContextSetUser(ctx, u) @@ -115,7 +117,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI } // validate the token and ensure access to the resource is allowed - u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, true) + u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, false) if err != nil { log.Warn().Err(err).Msg("access token is invalid") return nil, status.Errorf(codes.PermissionDenied, "auth: core access token is invalid") @@ -142,6 +144,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe } userGroupsCache = gcache.New(1000000).LFU().Build() + scopeExpansionCache = gcache.New(1000000).LFU().Build() h, ok := tokenmgr.NewFuncs[conf.TokenManager] if !ok { @@ -164,7 +167,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe // to decide the storage provider. tkn, ok := ctxpkg.ContextGetToken(ctx) if ok { - u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, false) + u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, true) if err == nil { // store user and scopes in context ctx = ctxpkg.ContextSetUser(ctx, u) @@ -184,7 +187,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe } // validate the token and ensure access to the resource is allowed - u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, true) + u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, false) if err != nil { log.Warn().Err(err).Msg("access token is invalid") return status.Errorf(codes.PermissionDenied, "auth: core access token is invalid") @@ -212,19 +215,21 @@ func (ss *wrappedServerStream) Context() context.Context { return ss.newCtx } -// dismantleToken extracts the user and scopes from the reva access token -func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string, fetchUserGroups bool) (*userpb.User, map[string]*authpb.Scope, error) { +func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string, unprotected bool) (*userpb.User, error) { u, tokenScope, err := mgr.DismantleToken(ctx, tkn) if err != nil { return nil, nil, err } - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return nil, nil, err + if unprotected { + return u, nil } - if sharedconf.SkipUserGroupsInToken() && fetchUserGroups { + if sharedconf.SkipUserGroupsInToken() { + client, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + return nil, err + } groups, err := getUserGroups(ctx, u, client) if err != nil { return nil, nil, err @@ -241,8 +246,8 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token. return u, tokenScope, nil } - if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr, mgr); err != nil { - return nil, nil, err + if err = expandAndVerifyScope(ctx, req, tokenScope, u, gatewayAddr, mgr); err != nil { + return nil, err } return u, tokenScope, nil diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go index 91868a7b5a..e157c6d30d 100644 --- a/internal/grpc/interceptors/auth/scope.go +++ b/internal/grpc/interceptors/auth/scope.go @@ -21,6 +21,7 @@ package auth import ( "context" "strings" + "time" appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" @@ -32,18 +33,24 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - statuspkg "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/token" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + statuspkg "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/token" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" "google.golang.org/grpc/metadata" ) -func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string, mgr token.Manager) error { +const ( + scopeDelimiter = "#" + scopeCacheExpiration = 3600 +) + +func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, user *userpb.User, gatewayAddr string, mgr token.Manager) error { log := appctx.GetLogger(ctx) client, err := pool.GetGatewayServiceClient(gatewayAddr) if err != nil { @@ -52,108 +59,63 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s hasEditorRole := false for _, v := range tokenScope { - if v.Role == authpb.Role_ROLE_EDITOR { + if v.Role == authpb.Role_ROLE_OWNER || v.Role == authpb.Role_ROLE_EDITOR { hasEditorRole = true + break } } if ref, ok := extractRef(req, hasEditorRole); ok { - // Check if req is of type *provider.Reference_Path - // If yes, the request might be coming from a share where the accessor is - // trying to impersonate the owner, since the share manager doesn't know the - // share path. - if ref.GetPath() != "" { - log.Info().Str("path", ref.GetPath()).Msg("resolving path reference to ID to check token scope") - for k := range tokenScope { - switch { - case strings.HasPrefix(k, "publicshare"): - var share link.PublicShare - err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) - if err != nil { - continue - } - if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok { - return nil - } + // The request is for a storage reference. This can be the case for multiple scenarios: + // - If the path is not empty, the request might be coming from a share where the accessor is + // trying to impersonate the owner, since the share manager doesn't know the + // share path. + // - If the ID not empty, the request might be coming from + // - a resource present inside a shared folder, or + // - a share created for a lightweight account after the token was minted. + log.Info().Msgf("resolving storage reference to check token scope %s", ref.String()) + for k := range tokenScope { + switch { + case strings.HasPrefix(k, "publicshare"): + if err = resolvePublicShare(ctx, ref, tokenScope[k], client, mgr); err == nil { + return nil + } - case strings.HasPrefix(k, "share"): - var share collaboration.Share - err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) - if err != nil { - continue - } - if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok { - return nil - } - case strings.HasPrefix(k, "lightweight"): - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil || shares.Status.Code != rpc.Code_CODE_OK { - log.Warn().Err(err).Msg("error listing received shares") - continue - } - for _, share := range shares.Shares { - if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok { - return nil - } - } + case strings.HasPrefix(k, "share"): + if err = resolveUserShare(ctx, ref, tokenScope[k], client, mgr); err == nil { + return nil } - } - } else { - // ref has ID present - // The request might be coming from - // - a resource present inside a shared folder, or - // - a share created for a lightweight account after the token was minted. - - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return err - } - for k := range tokenScope { - if strings.HasPrefix(k, "lightweight") { - log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId()) - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil || shares.Status.Code != rpc.Code_CODE_OK { - log.Warn().Err(err).Msg("error listing received shares") - continue - } - for _, share := range shares.Shares { - if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { - return nil - } - if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok { - return nil - } - } - } else if strings.HasPrefix(k, "publicshare") { - var share link.PublicShare - err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share) - if err != nil { - continue - } - if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok { - return nil - } + + case strings.HasPrefix(k, "lightweight"): + if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil { + return nil } } + log.Err(err).Msgf("error resolving reference %s under scope %+v", ref.String(), k) } } else if ref, ok := extractShareRef(req); ok { // It's a share ref // The request might be coming from a share created for a lightweight account // after the token was minted. - log.Info().Interface("reference", ref).Msg("resolving share reference against received shares to verify token scope") - client, err := pool.GetGatewayServiceClient(gatewayAddr) - if err != nil { - return err - } + log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref.String()) for k := range tokenScope { if strings.HasPrefix(k, "lightweight") { + // Check if this ID is cached + key := "lw:" + user.Id.OpaqueId + scopeDelimiter + ref.GetId().OpaqueId + if _, err := scopeExpansionCache.Get(key); err == nil { + return nil + } + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) if err != nil || shares.Status.Code != rpc.Code_CODE_OK { log.Warn().Err(err).Msg("error listing received shares") continue } for _, s := range shares.Shares { + shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + s.Share.Id.OpaqueId + _ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second) + if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId { return nil } @@ -168,6 +130,69 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s return errtypes.PermissionDenied("access to resource not allowed within the assigned scope") } +func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error { + // Check if this ref is cached + key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref) + if _, err := scopeExpansionCache.Get(key); err == nil { + return nil + } + + shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil || shares.Status.Code != rpc.Code_CODE_OK { + return errtypes.InternalError("error listing received shares") + } + + for _, share := range shares.Shares { + shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + resourceid.OwnCloudResourceIDWrap(share.Share.ResourceId) + _ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second) + + if ref.ResourceId != nil && utils.ResourceIDEqual(share.Share.ResourceId, ref.ResourceId) { + return nil + } + if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok { + _ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second) + return nil + } + } + + return errtypes.PermissionDenied("request is not for a nested resource") +} + +func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error { + var share link.PublicShare + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share) + if err != nil { + return err + } + + return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr) +} + +func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error { + var share collaboration.Share + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share) + if err != nil { + return err + } + + return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr) +} + +func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error { + // Check if this ref is cached + key := resourceid.OwnCloudResourceIDWrap(resource) + scopeDelimiter + getRefKey(ref) + if _, err := scopeExpansionCache.Get(key); err == nil { + return nil + } + + if ok, err := checkIfNestedResource(ctx, ref, resource, client, mgr); err == nil && ok { + _ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second) + return nil + } + + return errtypes.PermissionDenied("request is not for a nested resource") +} + func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) (bool, error) { // Since the resource ID is obtained from the scope, the current token // has access to it. @@ -185,7 +210,7 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent // We mint a token as the owner of the public share and try to stat the reference // TODO(ishank011): We need to find a better alternative to this - userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner}) + userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner, SkipFetchingUserGroups: true}) if err != nil || userResp.Status.Code != rpc.Code_CODE_OK { return false, err } @@ -297,3 +322,10 @@ func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) { } return nil, false } + +func getRefKey(ref *provider.Reference) string { + if ref.Path != "" { + return ref.Path + } + return resourceid.OwnCloudResourceIDWrap(ref.ResourceId) +} diff --git a/internal/grpc/interceptors/eventsmiddleware/conversion.go b/internal/grpc/interceptors/eventsmiddleware/conversion.go index 18d4ffc016..aa1ef2453d 100644 --- a/internal/grpc/interceptors/eventsmiddleware/conversion.go +++ b/internal/grpc/interceptors/eventsmiddleware/conversion.go @@ -19,238 +19,19 @@ package eventsmiddleware import ( - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/pkg/events" ) -// ShareCreated converts the response to an event +// ShareCreated converts response to event func ShareCreated(r *collaboration.CreateShareResponse) events.ShareCreated { - return events.ShareCreated{ + e := events.ShareCreated{ Sharer: r.Share.Creator, GranteeUserID: r.Share.GetGrantee().GetUserId(), GranteeGroupID: r.Share.GetGrantee().GetGroupId(), ItemID: r.Share.ResourceId, CTime: r.Share.Ctime, - Permissions: r.Share.Permissions, } -} - -// ShareRemoved converts the response to an event -func ShareRemoved(r *collaboration.RemoveShareResponse, req *collaboration.RemoveShareRequest) events.ShareRemoved { - return events.ShareRemoved{ - ShareID: req.Ref.GetId(), - ShareKey: req.Ref.GetKey(), - } -} - -// ShareUpdated converts the response to an event -func ShareUpdated(r *collaboration.UpdateShareResponse, req *collaboration.UpdateShareRequest) events.ShareUpdated { - updated := "" - if req.Field.GetPermissions() != nil { - updated = "permissions" - } else if req.Field.GetDisplayName() != "" { - updated = "displayname" - } - return events.ShareUpdated{ - ShareID: r.Share.Id, - ItemID: r.Share.ResourceId, - Permissions: r.Share.Permissions, - GranteeUserID: r.Share.GetGrantee().GetUserId(), - GranteeGroupID: r.Share.GetGrantee().GetGroupId(), - Sharer: r.Share.Creator, - MTime: r.Share.Mtime, - Updated: updated, - } -} - -// ReceivedShareUpdated converts the response to an event -func ReceivedShareUpdated(r *collaboration.UpdateReceivedShareResponse) events.ReceivedShareUpdated { - return events.ReceivedShareUpdated{ - ShareID: r.Share.Share.Id, - ItemID: r.Share.Share.ResourceId, - Permissions: r.Share.Share.Permissions, - GranteeUserID: r.Share.Share.GetGrantee().GetUserId(), - GranteeGroupID: r.Share.Share.GetGrantee().GetGroupId(), - Sharer: r.Share.Share.Creator, - MTime: r.Share.Share.Mtime, - State: collaboration.ShareState_name[int32(r.Share.State)], - } -} - -// LinkCreated converts the response to an event -func LinkCreated(r *link.CreatePublicShareResponse) events.LinkCreated { - return events.LinkCreated{ - ShareID: r.Share.Id, - Sharer: r.Share.Creator, - ItemID: r.Share.ResourceId, - Permissions: r.Share.Permissions, - DisplayName: r.Share.DisplayName, - Expiration: r.Share.Expiration, - PasswordProtected: r.Share.PasswordProtected, - CTime: r.Share.Ctime, - Token: r.Share.Token, - } -} -// LinkUpdated converts the response to an event -func LinkUpdated(r *link.UpdatePublicShareResponse, req *link.UpdatePublicShareRequest) events.LinkUpdated { - return events.LinkUpdated{ - ShareID: r.Share.Id, - Sharer: r.Share.Creator, - ItemID: r.Share.ResourceId, - Permissions: r.Share.Permissions, - DisplayName: r.Share.DisplayName, - Expiration: r.Share.Expiration, - PasswordProtected: r.Share.PasswordProtected, - CTime: r.Share.Ctime, - Token: r.Share.Token, - FieldUpdated: link.UpdatePublicShareRequest_Update_Type_name[int32(req.Update.GetType())], - } -} - -// LinkAccessed converts the response to an event -func LinkAccessed(r *link.GetPublicShareByTokenResponse) events.LinkAccessed { - return events.LinkAccessed{ - ShareID: r.Share.Id, - Sharer: r.Share.Creator, - ItemID: r.Share.ResourceId, - Permissions: r.Share.Permissions, - DisplayName: r.Share.DisplayName, - Expiration: r.Share.Expiration, - PasswordProtected: r.Share.PasswordProtected, - CTime: r.Share.Ctime, - Token: r.Share.Token, - } -} - -// LinkAccessFailed converts the response to an event -func LinkAccessFailed(r *link.GetPublicShareByTokenResponse, req *link.GetPublicShareByTokenRequest) events.LinkAccessFailed { - e := events.LinkAccessFailed{ - Status: r.Status.Code, - Message: r.Status.Message, - } - if r.Share != nil { - e.ShareID = r.Share.Id - e.Token = r.Share.Token - } return e } - -// LinkRemoved converts the response to an event -func LinkRemoved(r *link.RemovePublicShareResponse, req *link.RemovePublicShareRequest) events.LinkRemoved { - return events.LinkRemoved{ - ShareID: req.Ref.GetId(), - ShareToken: req.Ref.GetToken(), - } -} - -// FileUploaded converts the response to an event -func FileUploaded(r *provider.InitiateFileUploadResponse, req *provider.InitiateFileUploadRequest) events.FileUploaded { - return events.FileUploaded{ - FileID: req.Ref, - } -} - -// FileDownloaded converts the response to an event -func FileDownloaded(r *provider.InitiateFileDownloadResponse, req *provider.InitiateFileDownloadRequest) events.FileDownloaded { - return events.FileDownloaded{ - FileID: req.Ref, - } -} - -// ItemTrashed converts the response to an event -func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest) events.ItemTrashed { - return events.ItemTrashed{ - FileID: req.Ref, - } -} - -// ItemMoved converts the response to an event -func ItemMoved(r *provider.MoveResponse, req *provider.MoveRequest) events.ItemMoved { - return events.ItemMoved{ - FileID: req.Destination, - OldReference: req.Source, - } -} - -// ItemPurged converts the response to an event -func ItemPurged(r *provider.PurgeRecycleResponse, req *provider.PurgeRecycleRequest) events.ItemPurged { - return events.ItemPurged{ - FileID: req.Ref, - } -} - -// ItemRestored converts the response to an event -func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreRecycleItemRequest) events.ItemRestored { - ref := req.Ref - if req.RestoreRef != nil { - ref = req.RestoreRef - } - return events.ItemRestored{ - FileID: ref, - OldReference: req.Ref, - Key: req.Key, - } -} - -// FileVersionRestored converts the response to an event -func FileVersionRestored(r *provider.RestoreFileVersionResponse, req *provider.RestoreFileVersionRequest) events.FileVersionRestored { - return events.FileVersionRestored{ - FileID: req.Ref, - Key: req.Key, - } -} - -// SpaceCreated converts the response to an event -func SpaceCreated(r *provider.CreateStorageSpaceResponse) events.SpaceCreated { - return events.SpaceCreated{ - ID: r.StorageSpace.Id, - Owner: extractOwner(r.StorageSpace.Owner), - Root: r.StorageSpace.Root, - Name: r.StorageSpace.Name, - Type: r.StorageSpace.SpaceType, - Quota: r.StorageSpace.Quota, - MTime: r.StorageSpace.Mtime, - } -} - -// SpaceRenamed converts the response to an event -func SpaceRenamed(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateStorageSpaceRequest) events.SpaceRenamed { - return events.SpaceRenamed{ - ID: r.StorageSpace.Id, - Owner: extractOwner(r.StorageSpace.Owner), - Name: r.StorageSpace.Name, - } -} - -// SpaceEnabled converts the response to an event -func SpaceEnabled(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateStorageSpaceRequest) events.SpaceEnabled { - return events.SpaceEnabled{ - ID: r.StorageSpace.Id, - Owner: extractOwner(r.StorageSpace.Owner), - } -} - -// SpaceDisabled converts the response to an event -func SpaceDisabled(r *provider.DeleteStorageSpaceResponse, req *provider.DeleteStorageSpaceRequest) events.SpaceDisabled { - return events.SpaceDisabled{ - ID: req.Id, - } -} - -// SpaceDeleted converts the response to an event -func SpaceDeleted(r *provider.DeleteStorageSpaceResponse, req *provider.DeleteStorageSpaceRequest) events.SpaceDeleted { - return events.SpaceDeleted{ - ID: req.Id, - } -} - -func extractOwner(u *user.User) *user.UserId { - if u != nil { - return u.Id - } - return nil -} diff --git a/internal/grpc/interceptors/eventsmiddleware/events.go b/internal/grpc/interceptors/eventsmiddleware/events.go index 6daf73eb07..d7603e74c5 100644 --- a/internal/grpc/interceptors/eventsmiddleware/events.go +++ b/internal/grpc/interceptors/eventsmiddleware/events.go @@ -25,16 +25,11 @@ import ( "go-micro.dev/v4/util/log" "google.golang.org/grpc" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - v1beta12 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/asim/go-micro/plugins/events/nats/v4" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/events/server" - "github.com/cs3org/reva/v2/pkg/rgrpc" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/cs3org/reva/pkg/events" + "github.com/cs3org/reva/pkg/events/server" + "github.com/cs3org/reva/pkg/rgrpc" ) const ( @@ -63,91 +58,7 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error var ev interface{} switch v := res.(type) { case *collaboration.CreateShareResponse: - if isSuccess(v) { - ev = ShareCreated(v) - } - case *collaboration.RemoveShareResponse: - if isSuccess(v) { - ev = ShareRemoved(v, req.(*collaboration.RemoveShareRequest)) - } - case *collaboration.UpdateShareResponse: - if isSuccess(v) { - ev = ShareUpdated(v, req.(*collaboration.UpdateShareRequest)) - } - case *collaboration.UpdateReceivedShareResponse: - if isSuccess(v) { - ev = ReceivedShareUpdated(v) - } - case *link.CreatePublicShareResponse: - if isSuccess(v) { - ev = LinkCreated(v) - } - case *link.UpdatePublicShareResponse: - if isSuccess(v) { - ev = LinkUpdated(v, req.(*link.UpdatePublicShareRequest)) - } - case *link.RemovePublicShareResponse: - if isSuccess(v) { - ev = LinkRemoved(v, req.(*link.RemovePublicShareRequest)) - } - case *link.GetPublicShareByTokenResponse: - if isSuccess(v) { - ev = LinkAccessed(v) - } else { - ev = LinkAccessFailed(v, req.(*link.GetPublicShareByTokenRequest)) - } - case *provider.InitiateFileUploadResponse: - if isSuccess(v) { - ev = FileUploaded(v, req.(*provider.InitiateFileUploadRequest)) - } - case *provider.InitiateFileDownloadResponse: - if isSuccess(v) { - ev = FileDownloaded(v, req.(*provider.InitiateFileDownloadRequest)) - } - case *provider.DeleteResponse: - if isSuccess(v) { - ev = ItemTrashed(v, req.(*provider.DeleteRequest)) - } - case *provider.MoveResponse: - if isSuccess(v) { - ev = ItemMoved(v, req.(*provider.MoveRequest)) - } - case *provider.PurgeRecycleResponse: - if isSuccess(v) { - ev = ItemPurged(v, req.(*provider.PurgeRecycleRequest)) - } - case *provider.RestoreRecycleItemResponse: - if isSuccess(v) { - ev = ItemRestored(v, req.(*provider.RestoreRecycleItemRequest)) - } - case *provider.RestoreFileVersionResponse: - if isSuccess(v) { - ev = FileVersionRestored(v, req.(*provider.RestoreFileVersionRequest)) - } - case *provider.CreateStorageSpaceResponse: - if isSuccess(v) && v.StorageSpace != nil { // TODO: Why are there CreateStorageSpaceResponses with nil StorageSpace? - ev = SpaceCreated(v) - } - case *provider.UpdateStorageSpaceResponse: - if isSuccess(v) { - r := req.(*provider.UpdateStorageSpaceRequest) - if r.StorageSpace.Name != "" { - ev = SpaceRenamed(v, r) - } - - if utils.ExistsInOpaque(r.Opaque, "restore") { - ev = SpaceEnabled(v, r) - } - } - case *provider.DeleteStorageSpaceResponse: - if isSuccess(v) { - r := req.(*provider.DeleteStorageSpaceRequest) - if utils.ExistsInOpaque(r.Opaque, "purge") { - ev = SpaceDeleted(v, r) - } else { - ev = SpaceDisabled(v, r) - } - } + ev = ShareCreated(v) } if ev != nil { @@ -171,15 +82,6 @@ func NewStream() grpc.StreamServerInterceptor { return interceptor } -// common interface to all responses -type su interface { - GetStatus() *v1beta12.Status -} - -func isSuccess(res su) bool { - return res.GetStatus().Code == rpc.Code_CODE_OK -} - func publisherFromConfig(m map[string]interface{}) (events.Publisher, error) { typ := m["type"].(string) switch typ { @@ -188,6 +90,6 @@ func publisherFromConfig(m map[string]interface{}) (events.Publisher, error) { case "nats": address := m["address"].(string) cid := m["clusterID"].(string) - return server.NewNatsStream(natsjs.Address(address), natsjs.ClusterID(cid)) + return server.NewNatsStream(nats.Address(address), nats.ClusterID(cid)) } } diff --git a/internal/grpc/interceptors/loader/loader.go b/internal/grpc/interceptors/loader/loader.go index f2b7d9238d..f0d525f045 100644 --- a/internal/grpc/interceptors/loader/loader.go +++ b/internal/grpc/interceptors/loader/loader.go @@ -20,7 +20,7 @@ package loader import ( // Load core GRPC services - _ "github.com/cs3org/reva/v2/internal/grpc/interceptors/eventsmiddleware" - _ "github.com/cs3org/reva/v2/internal/grpc/interceptors/readonly" + _ "github.com/cs3org/reva/internal/grpc/interceptors/eventsmiddleware" + _ "github.com/cs3org/reva/internal/grpc/interceptors/readonly" // Add your own service here ) diff --git a/internal/grpc/interceptors/recovery/recovery.go b/internal/grpc/interceptors/recovery/recovery.go index ba3d1ec60f..37f093d6d9 100644 --- a/internal/grpc/interceptors/recovery/recovery.go +++ b/internal/grpc/interceptors/recovery/recovery.go @@ -47,6 +47,6 @@ func NewStream() grpc.StreamServerInterceptor { func recoveryFunc(ctx context.Context, p interface{}) (err error) { debug.PrintStack() log := appctx.GetLogger(ctx) - log.Error().Msgf("%+v", p) + log.Error().Msgf("%+v; stack: %s", p, debug.Stack()) return status.Errorf(codes.Internal, "%s", p) } diff --git a/internal/grpc/services/authprovider/authprovider.go b/internal/grpc/services/authprovider/authprovider.go index 5145285ea2..29acbbe629 100644 --- a/internal/grpc/services/authprovider/authprovider.go +++ b/internal/grpc/services/authprovider/authprovider.go @@ -138,7 +138,7 @@ func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRe u, scope, err := s.authmgr.Authenticate(ctx, username, password) switch v := err.(type) { case nil: - log.Info().Str("user", u.String()).Msg("user authenticated") + log.Info().Msgf("user %s authenticated", u.Id) return &provider.AuthenticateResponse{ Status: status.NewOK(ctx), User: u, diff --git a/internal/grpc/services/authregistry/authregistry.go b/internal/grpc/services/authregistry/authregistry.go index 3df1d1852d..f32527c644 100644 --- a/internal/grpc/services/authregistry/authregistry.go +++ b/internal/grpc/services/authregistry/authregistry.go @@ -120,15 +120,13 @@ func (s *service) GetAuthProviders(ctx context.Context, req *registrypb.GetAuthP pinfo, err := s.reg.GetProvider(ctx, req.Type) if err != nil { return ®istrypb.GetAuthProvidersResponse{ - Status: status.NewInternal(ctx, "error getting auth provider for type: "+req.Type), + Status: status.NewInternal(ctx, err, "error getting auth provider for type: "+req.Type), }, nil } res := ®istrypb.GetAuthProvidersResponse{ - Status: status.NewOK(ctx), - Providers: []*registrypb.ProviderInfo{ - pinfo, - }, + Status: status.NewOK(ctx), + Providers: []*registrypb.ProviderInfo{pinfo}, } return res, nil } diff --git a/internal/grpc/services/datatx/datatx.go b/internal/grpc/services/datatx/datatx.go index 6898fa9811..8c2c2b87e2 100644 --- a/internal/grpc/services/datatx/datatx.go +++ b/internal/grpc/services/datatx/datatx.go @@ -20,11 +20,21 @@ package datatx import ( "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "os" + "sync" + ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + txdriver "github.com/cs3org/reva/pkg/datatx" + txregistry "github.com/cs3org/reva/pkg/datatx/manager/registry" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc" + "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/grpc" @@ -35,23 +45,72 @@ func init() { } type config struct { + // transfer driver + TxDriver string `mapstructure:"txdriver"` + TxDrivers map[string]map[string]interface{} `mapstructure:"txdrivers"` + // storage driver to persist share/transfer relation + StorageDriver string `mapstructure:"storage_driver"` + StorageDrivers map[string]map[string]interface{} `mapstructure:"storage_drivers"` + TxSharesFile string `mapstructure:"tx_shares_file"` + DataTransfersFolder string `mapstructure:"data_transfers_folder"` } type service struct { - conf *config + conf *config + txManager txdriver.Manager + txShareDriver *txShareDriver +} + +type txShareDriver struct { + sync.Mutex // concurrent access to the file + model *txShareModel +} +type txShareModel struct { + File string + TxShares map[string]*txShare `json:"shares"` +} + +type txShare struct { + TxID string + SrcTargetURI string + DestTargetURI string + Opaque *types.Opaque `json:"opaque"` +} + +type webdavEndpoint struct { + filePath string + endpoint string + endpointScheme string + token string } func (c *config) init() { + if c.TxDriver == "" { + c.TxDriver = "rclone" + } + if c.TxSharesFile == "" { + c.TxSharesFile = "/var/tmp/reva/datatx-shares.json" + } + if c.DataTransfersFolder == "" { + c.DataTransfersFolder = "/home/DataTransfers" + } } func (s *service) Register(ss *grpc.Server) { datatx.RegisterTxAPIServer(ss, s) } +func getDatatxManager(c *config) (txdriver.Manager, error) { + if f, ok := txregistry.NewFuncs[c.TxDriver]; ok { + return f(c.TxDrivers[c.TxDriver]) + } + return nil, errtypes.NotFound("datatx service: driver not found: " + c.TxDriver) +} + func parseConfig(m map[string]interface{}) (*config, error) { c := &config{} if err := mapstructure.Decode(m, c); err != nil { - err = errors.Wrap(err, "error decoding conf") + err = errors.Wrap(err, "datatx service: error decoding conf") return nil, err } return c, nil @@ -66,8 +125,24 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { } c.init() + txManager, err := getDatatxManager(c) + if err != nil { + return nil, err + } + + model, err := loadOrCreate(c.TxSharesFile) + if err != nil { + err = errors.Wrap(err, "datatx service: error loading the file containing the transfer shares") + return nil, err + } + txShareDriver := &txShareDriver{ + model: model, + } + service := &service{ - conf: c, + conf: c, + txManager: txManager, + txShareDriver: txShareDriver, } return service, nil @@ -82,31 +157,227 @@ func (s *service) UnprotectedEndpoints() []string { } func (s *service) PullTransfer(ctx context.Context, req *datatx.PullTransferRequest) (*datatx.PullTransferResponse, error) { + srcEp, err := s.extractEndpointInfo(ctx, req.SrcTargetUri) + if err != nil { + return nil, err + } + srcRemote := fmt.Sprintf("%s://%s", srcEp.endpointScheme, srcEp.endpoint) + srcPath := srcEp.filePath + srcToken := srcEp.token + + destEp, err := s.extractEndpointInfo(ctx, req.DestTargetUri) + if err != nil { + return nil, err + } + dstRemote := fmt.Sprintf("%s://%s", destEp.endpointScheme, destEp.endpoint) + dstPath := destEp.filePath + dstToken := destEp.token + + txInfo, startTransferErr := s.txManager.StartTransfer(ctx, srcRemote, srcPath, srcToken, dstRemote, dstPath, dstToken) + + // we always save the transfer regardless of start transfer outcome + // only then, if starting fails, can we try to restart it + txShare := &txShare{ + TxID: txInfo.GetId().OpaqueId, + SrcTargetURI: req.SrcTargetUri, + DestTargetURI: req.DestTargetUri, + Opaque: req.Opaque, + } + s.txShareDriver.Lock() + defer s.txShareDriver.Unlock() + + s.txShareDriver.model.TxShares[txInfo.GetId().OpaqueId] = txShare + if err := s.txShareDriver.model.saveTxShare(); err != nil { + err = errors.Wrap(err, "datatx service: error saving transfer share: "+datatx.Status_STATUS_INVALID.String()) + return &datatx.PullTransferResponse{ + Status: status.NewInvalid(ctx, "error pulling transfer"), + }, err + } + + // now check start transfer outcome + if startTransferErr != nil { + startTransferErr = errors.Wrap(startTransferErr, "datatx service: error starting transfer job") + return &datatx.PullTransferResponse{ + Status: status.NewInvalid(ctx, "datatx service: error pulling transfer"), + TxInfo: txInfo, + }, startTransferErr + } + return &datatx.PullTransferResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("PullTransfer not implemented"), "PullTransfer not implemented"), - }, nil + Status: status.NewOK(ctx), + TxInfo: txInfo, + }, err } -func (s *service) GetTransferStatus(ctx context.Context, in *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) { +func (s *service) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) { + txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()] + if !ok { + return nil, errtypes.InternalError("datatx service: transfer not found") + } + + txInfo, err := s.txManager.GetTransferStatus(ctx, req.GetTxId().OpaqueId) + if err != nil { + err = errors.Wrap(err, "datatx service: error retrieving transfer status") + return &datatx.GetTransferStatusResponse{ + Status: status.NewInternal(ctx, err, "datatx service: error getting transfer status"), + TxInfo: txInfo, + }, err + } + + txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)} + return &datatx.GetTransferStatusResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("GetTransferStatus not implemented"), "GetTransferStatus not implemented"), + Status: status.NewOK(ctx), + TxInfo: txInfo, + }, nil +} + +func (s *service) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) { + txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()] + if !ok { + return nil, errtypes.InternalError("datatx service: transfer not found") + } + + txInfo, err := s.txManager.CancelTransfer(ctx, req.GetTxId().OpaqueId) + if err != nil { + txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)} + err = errors.Wrap(err, "datatx service: error cancelling transfer") + return &datatx.CancelTransferResponse{ + Status: status.NewInternal(ctx, err, "error cancelling transfer"), + TxInfo: txInfo, + }, err + } + + txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)} + + return &datatx.CancelTransferResponse{ + Status: status.NewOK(ctx), + TxInfo: txInfo, }, nil } -func (s *service) ListTransfers(ctx context.Context, in *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) { +func (s *service) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) { + filters := req.Filters + var txInfos []*datatx.TxInfo + for _, txShare := range s.txShareDriver.model.TxShares { + if len(filters) == 0 { + txInfos = append(txInfos, &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txShare.TxID}, + ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}, + }) + } else { + for _, f := range filters { + if f.Type == datatx.ListTransfersRequest_Filter_TYPE_SHARE_ID { + if f.GetShareId().GetOpaqueId() == string(txShare.Opaque.Map["shareId"].Value) { + txInfos = append(txInfos, &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txShare.TxID}, + ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}, + }) + } + } + } + } + } + return &datatx.ListTransfersResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("ListTransfers not implemented"), "ListTransfers not implemented"), + Status: status.NewOK(ctx), + Transfers: txInfos, }, nil } -func (s *service) RetryTransfer(ctx context.Context, in *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) { +func (s *service) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) { + txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()] + if !ok { + return nil, errtypes.InternalError("datatx service: transfer not found") + } + + txInfo, err := s.txManager.RetryTransfer(ctx, req.GetTxId().OpaqueId) + if err != nil { + err = errors.Wrap(err, "datatx service: error retrying transfer") + return &datatx.RetryTransferResponse{ + Status: status.NewInternal(ctx, err, "error retrying transfer"), + TxInfo: txInfo, + }, err + } + + txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)} + return &datatx.RetryTransferResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("RetryTransfer not implemented"), "RetryTransfer not implemented"), + Status: status.NewOK(ctx), + TxInfo: txInfo, }, nil } -func (s *service) CancelTransfer(ctx context.Context, in *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) { - return &datatx.CancelTransferResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("CancelTransfer not implemented"), "CancelTransfer not implemented"), +func (s *service) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) { + if targetURL == "" { + return nil, errtypes.BadRequest("datatx service: ref target is an empty uri") + } + + uri, err := url.Parse(targetURL) + if err != nil { + return nil, errors.Wrap(err, "datatx service: error parsing target uri: "+targetURL) + } + + m, err := url.ParseQuery(uri.RawQuery) + if err != nil { + return nil, errors.Wrap(err, "datatx service: error parsing target resource name") + } + + return &webdavEndpoint{ + filePath: m["name"][0], + endpoint: uri.Host + uri.Path, + endpointScheme: uri.Scheme, + token: uri.User.String(), }, nil } + +func loadOrCreate(file string) (*txShareModel, error) { + _, err := os.Stat(file) + if os.IsNotExist(err) { + if err := ioutil.WriteFile(file, []byte("{}"), 0700); err != nil { + err = errors.Wrap(err, "datatx service: error creating the transfer shares storage file: "+file) + return nil, err + } + } + + fd, err := os.OpenFile(file, os.O_CREATE, 0644) + if err != nil { + err = errors.Wrap(err, "datatx service: error opening the transfer shares storage file: "+file) + return nil, err + } + defer fd.Close() + + data, err := ioutil.ReadAll(fd) + if err != nil { + err = errors.Wrap(err, "datatx service: error reading the data") + return nil, err + } + + model := &txShareModel{} + if err := json.Unmarshal(data, model); err != nil { + err = errors.Wrap(err, "datatx service: error decoding transfer shares data to json") + return nil, err + } + + if model.TxShares == nil { + model.TxShares = make(map[string]*txShare) + } + + model.File = file + return model, nil +} + +func (m *txShareModel) saveTxShare() error { + data, err := json.Marshal(m) + if err != nil { + err = errors.Wrap(err, "datatx service: error encoding transfer share data to json") + return err + } + + if err := ioutil.WriteFile(m.File, data, 0644); err != nil { + err = errors.Wrap(err, "datatx service: error writing transfer share data to file: "+m.File) + return err + } + + return nil +} diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 312164ee51..f4822f383d 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -21,24 +21,19 @@ package gateway import ( "context" "fmt" - "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/sharedconf" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/sharedconf" "github.com/pkg/errors" "google.golang.org/grpc/metadata" ) @@ -119,14 +114,17 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest ctx = ctxpkg.ContextSetUser(ctx, res.User) ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token) - // TODO(ishank011): Add a cache for these - scope, err := s.expandScopes(ctx, res.TokenScope) + // Commenting out as the token size can get too big + // For now, we'll try to resolve all resources on every request and cache those + /* scope, err := s.expandScopes(ctx, res.TokenScope) if err != nil { err = errors.Wrap(err, "authsvc: error expanding token scope") return &gateway.AuthenticateResponse{ Status: status.NewUnauthenticated(ctx, err, "error expanding access token scope"), }, nil } + */ + scope := res.TokenScope // scope := res.TokenScope @@ -237,6 +235,7 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (authpb.Pro return nil, errtypes.InternalError("gateway: error finding an auth provider for type: " + authType) } +/* func (s *svc) expandScopes(ctx context.Context, scopeMap map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { log := appctx.GetLogger(ctx) newMap := make(map[string]*authpb.Scope) @@ -307,3 +306,4 @@ func (s *svc) statAndAddResource(ctx context.Context, r *storageprovider.Resourc return scope.AddResourceInfoScope(statResponse.Info, role, scopeMap) } +*/ diff --git a/internal/grpc/services/gateway/datatx.go b/internal/grpc/services/gateway/datatx.go index 491e4b04ca..7f5bf7e3c2 100644 --- a/internal/grpc/services/gateway/datatx.go +++ b/internal/grpc/services/gateway/datatx.go @@ -30,8 +30,9 @@ import ( func (s *svc) PullTransfer(ctx context.Context, req *datatx.PullTransferRequest) (*datatx.PullTransferResponse, error) { c, err := pool.GetDataTxClient(s.c.DataTxEndpoint) if err != nil { + err = errors.Wrap(err, "gateway: error calling GetDataTxClient") return &datatx.PullTransferResponse{ - Status: status.NewInternal(ctx, "error getting data transfer client"), + Status: status.NewInternal(ctx, err, "error getting data transfer client"), }, nil } @@ -43,65 +44,69 @@ func (s *svc) PullTransfer(ctx context.Context, req *datatx.PullTransferRequest) return res, nil } -func (s *svc) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) { +func (s *svc) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) { c, err := pool.GetDataTxClient(s.c.DataTxEndpoint) if err != nil { - return &datatx.ListTransfersResponse{ + err = errors.Wrap(err, "gateway: error calling GetDataTxClient") + return &datatx.GetTransferStatusResponse{ Status: status.NewInternal(ctx, "error getting data transfer client"), }, nil } - res, err := c.ListTransfers(ctx, req) + res, err := c.GetTransferStatus(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling ListTransfers") + return nil, errors.Wrap(err, "gateway: error calling GetTransferStatus") } return res, nil } -func (s *svc) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) { +func (s *svc) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) { c, err := pool.GetDataTxClient(s.c.DataTxEndpoint) if err != nil { - return &datatx.RetryTransferResponse{ + err = errors.Wrap(err, "gateway: error calling GetDataTxClient") + return &datatx.CancelTransferResponse{ Status: status.NewInternal(ctx, "error getting data transfer client"), }, nil } - res, err := c.RetryTransfer(ctx, req) + res, err := c.CancelTransfer(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling RetryTransfer") + return nil, errors.Wrap(err, "gateway: error calling CancelTransfer") } return res, nil } -func (s *svc) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) { +func (s *svc) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) { c, err := pool.GetDataTxClient(s.c.DataTxEndpoint) if err != nil { - return &datatx.GetTransferStatusResponse{ - Status: status.NewInternal(ctx, "error getting data transfer client"), + err = errors.Wrap(err, "gateway: error calling GetDataTxClient") + return &datatx.ListTransfersResponse{ + Status: status.NewInternal(ctx, err, "error getting data transfer client"), }, nil } - res, err := c.GetTransferStatus(ctx, req) + res, err := c.ListTransfers(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling GetTransferStatus") + return nil, errors.Wrap(err, "gateway: error calling ListTransfers") } return res, nil } -func (s *svc) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) { +func (s *svc) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) { c, err := pool.GetDataTxClient(s.c.DataTxEndpoint) if err != nil { - return &datatx.CancelTransferResponse{ - Status: status.NewInternal(ctx, "error getting data transfer client"), + err = errors.Wrap(err, "gateway: error calling GetDataTxClient") + return &datatx.RetryTransferResponse{ + Status: status.NewInternal(ctx, err, "error getting data transfer client"), }, nil } - res, err := c.CancelTransfer(ctx, req) + res, err := c.RetryTransfer(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling CancelTransfer") + return nil, errors.Wrap(err, "gateway: error calling RetryTransfer") } return res, nil diff --git a/internal/grpc/services/gateway/gateway.go b/internal/grpc/services/gateway/gateway.go index 66315918e1..bb22c80565 100644 --- a/internal/grpc/services/gateway/gateway.go +++ b/internal/grpc/services/gateway/gateway.go @@ -84,7 +84,7 @@ func (c *config) init() { c.ShareFolder = strings.Trim(c.ShareFolder, "/") if c.DataTransfersFolder == "" { - c.DataTransfersFolder = "Data-Transfers" + c.DataTransfersFolder = "DataTransfers" } if c.TokenManager == "" { diff --git a/internal/grpc/services/gateway/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index 5f0dc925e9..a930dfcfa1 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -21,15 +21,21 @@ package gateway import ( "context" "fmt" + "net/url" "path" + "strings" + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/pkg/errors" ) @@ -257,6 +263,124 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive panic("gateway: error updating a received share: the share is nil") } + if share.GetShare().ShareType == ocm.Share_SHARE_TYPE_TRANSFER { + srcIdp := share.GetShare().GetOwner().GetIdp() + meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ + Domain: srcIdp, + }) + if err != nil { + log.Err(err).Msg("gateway: error calling GetInfoByDomain") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_INTERNAL}, + }, nil + } + var srcEndpoint string + var srcEndpointBaseURI string + // target URI scheme will be the webdav endpoint scheme + var srcEndpointScheme string + for _, s := range meshProvider.ProviderInfo.Services { + if strings.ToLower(s.Endpoint.Type.Name) == "webdav" { + url, err := url.Parse(s.Endpoint.Path) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare: unable to parse webdav endpoint " + s.Endpoint.Path) + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_INTERNAL}, + }, nil + } + srcEndpoint = url.Host + srcEndpointBaseURI = url.Path + srcEndpointScheme = url.Scheme + break + } + } + + var srcToken string + srcTokenOpaque, ok := share.GetShare().Grantee.Opaque.Map["token"] + if !ok { + return &ocm.UpdateReceivedOCMShareResponse{ + Status: status.NewNotFound(ctx, "token not found"), + }, nil + } + switch srcTokenOpaque.Decoder { + case "plain": + srcToken = string(srcTokenOpaque.Value) + default: + err := errtypes.NotSupported("opaque entry decoder not recognized: " + srcTokenOpaque.Decoder) + return &ocm.UpdateReceivedOCMShareResponse{ + Status: status.NewInternal(ctx, err, "error updating received share"), + }, nil + } + + srcPath := path.Join(srcEndpointBaseURI, share.GetShare().Name) + srcTargetURI := fmt.Sprintf("%s://%s@%s?name=%s", srcEndpointScheme, srcToken, srcEndpoint, srcPath) + + // get the webdav endpoint of the grantee's idp + var granteeIdp string + if share.GetShare().GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_USER { + granteeIdp = share.GetShare().GetGrantee().GetUserId().Idp + } + if share.GetShare().GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + granteeIdp = share.GetShare().GetGrantee().GetGroupId().Idp + } + destWebdavEndpoint, err := s.getWebdavEndpoint(ctx, granteeIdp) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_INTERNAL}, + }, nil + } + url, err := url.Parse(destWebdavEndpoint) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare: unable to parse webdav endpoint " + destWebdavEndpoint) + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_INTERNAL}, + }, nil + } + destEndpoint := url.Host + destEndpointBaseURI := url.Path + destEndpointScheme := url.Scheme + destToken := ctxpkg.ContextMustGetToken(ctx) + homeRes, err := s.GetHome(ctx, &provider.GetHomeRequest{}) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_INTERNAL}, + }, nil + } + destPath := path.Join(destEndpointBaseURI, homeRes.Path, s.c.DataTransfersFolder, path.Base(share.GetShare().Name)) + destTargetURI := fmt.Sprintf("%s://%s@%s?name=%s", destEndpointScheme, destToken, destEndpoint, destPath) + + opaqueObj := &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "shareId": { + Decoder: "plain", + Value: []byte(share.GetShare().GetId().OpaqueId), + }, + }, + } + req := &datatx.PullTransferRequest{ + SrcTargetUri: srcTargetURI, + DestTargetUri: destTargetURI, + Opaque: opaqueObj, + } + res, err := s.PullTransfer(ctx, req) + if err != nil { + log.Err(err).Msg("gateway: error calling PullTransfer") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, err + } + + log.Info().Msgf("gateway: PullTransfer: %v", res.TxInfo) + + // do not create an OCM reference, just return + return &ocm.UpdateReceivedOCMShareResponse{ + Status: status.NewOK(ctx), + }, nil + } + createRefStatus, err := s.createOCMReference(ctx, share.Share) return &ocm.UpdateReceivedOCMShareResponse{ Status: createRefStatus, diff --git a/internal/grpc/services/gateway/permissions.go b/internal/grpc/services/gateway/permissions.go index ad7ef50122..2b1806633a 100644 --- a/internal/grpc/services/gateway/permissions.go +++ b/internal/grpc/services/gateway/permissions.go @@ -22,15 +22,17 @@ import ( "context" permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/pkg/errors" ) func (s *svc) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { c, err := pool.GetPermissionsClient(s.c.PermissionsEndpoint) if err != nil { + err = errors.Wrap(err, "gateway: error calling GetPermissionssClient") return &permissions.CheckPermissionResponse{ - Status: status.NewInternal(ctx, "error getting permissions client"), + Status: status.NewInternal(ctx, err, "error getting permissions client"), }, nil } return c.CheckPermission(ctx, req) diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index e076185c81..9baadbee08 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -600,17 +600,1132 @@ func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainer c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref) if err != nil { return &provider.CreateContainerResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewStatusFromErrType(ctx, "createContainer ref="+req.Ref.String(), err), }, nil } res, err := c.CreateContainer(ctx, req) if err != nil { - return &provider.CreateContainerResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call CreateContainer", err), + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.CreateContainerResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling CreateContainer") + } + + return res, nil +} + +func (s *svc) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.TouchFileResponse{ + Status: status.NewStatusFromErrType(ctx, "TouchFile ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.TouchFile(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.TouchFileResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling TouchFile") + } + + return res, nil +} + +// check if the path contains the prefix of the shared folder +func (s *svc) inSharedFolder(ctx context.Context, p string) bool { + sharedFolder := s.getSharedFolder(ctx) + return strings.HasPrefix(p, sharedFolder) +} + +func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { + log := appctx.GetLogger(ctx) + p, st := s.getPath(ctx, req.Ref) + if st.Code != rpc.Code_CODE_OK { + return &provider.DeleteResponse{ + Status: st, + }, nil + } + + ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "Delete") + defer span.End() + + if !s.inSharedFolder(ctx, p) { + return s.delete(ctx, req) + } + + if s.isSharedFolder(ctx, p) { + // TODO(labkode): deleting share names should be allowed, means unmounting. + err := errtypes.BadRequest("gateway: cannot delete share folder or share name: path=" + p) + span.RecordError(err) + return &provider.DeleteResponse{ + Status: status.NewInvalidArg(ctx, "path points to share folder or share name"), + }, nil + + } + + if s.isShareName(ctx, p) { + log.Debug().Msgf("path:%s points to share name", p) + + sRes, err := s.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + return nil, err + } + + statRes, err := s.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: p, + }, + }) + if err != nil { + return nil, err + } + + // the following will check that: + // - the resource to delete is a share the current user received + // - signal the storage the delete must not land in the trashbin + // - delete the resource and update the share status to "rejected" + for _, share := range sRes.Shares { + if statRes != nil && (share.Share.ResourceId.OpaqueId == statRes.Info.Id.OpaqueId) && (share.Share.ResourceId.StorageId == statRes.Info.Id.StorageId) { + // this opaque needs explanation. It signals the storage the resource we're about to delete does not + // belong to the current user because it was share to her, thus delete the "node" and don't send it to + // the trash bin, since the share can be mounted as many times as desired. + req.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "deleting_shared_resource": { + Value: []byte("true"), + Decoder: "plain", + }, + }, + } + + // the following block takes care of updating the state of the share to "rejected". This will ensure the user + // can "Accept" the share once again. + // TODO should this be pending? If so, update the two comments above as well. If not, get rid of this comment. + share.State = collaboration.ShareState_SHARE_STATE_REJECTED + r := &collaboration.UpdateReceivedShareRequest{ + Share: share, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, + } + + _, err := s.UpdateReceivedShare(ctx, r) + if err != nil { + return nil, err + } + + return &provider.DeleteResponse{ + Status: status.NewOK(ctx), + }, nil + } + } + + return &provider.DeleteResponse{ + Status: status.NewNotFound(ctx, "could not find share"), + }, nil + } + + if s.isShareChild(ctx, p) { + shareName, shareChild := s.splitShare(ctx, p) + log.Debug().Msgf("path:%s sharename:%s sharechild: %s", p, shareName, shareChild) + + ref := &provider.Reference{Path: shareName} + + statReq := &provider.StatRequest{Ref: ref} + statRes, err := s.stat(ctx, statReq) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.DeleteResponse{ + Status: statRes.Status, + }, nil + } + + ri, protocol, err := s.checkRef(ctx, statRes.Info) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), + }, nil + } + + if protocol == "webdav" { + err = s.webdavRefDelete(ctx, statRes.Info.Target, shareChild) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "gateway: error deleting resource on webdav host: "+p), + }, nil + } + return &provider.DeleteResponse{ + Status: status.NewOK(ctx), + }, nil + } + + // append child to target + req.Ref.Path = path.Join(ri.Path, shareChild) + return s.delete(ctx, req) + } + + panic("gateway: delete called on unknown path:" + p) +} + +func (s *svc) delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { + // TODO(ishank011): enable deleting references spread across storage providers, eg. /eos + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewStatusFromErrType(ctx, "delete ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.Delete(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.DeleteResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling Delete") + } + + return res, nil +} + +func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { + log := appctx.GetLogger(ctx) + p, st := s.getPath(ctx, req.Source) + if st.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: st, + }, nil + } + + dp, st := s.getPath(ctx, req.Destination) + if st.Code != rpc.Code_CODE_OK && st.Code != rpc.Code_CODE_NOT_FOUND { + return &provider.MoveResponse{ + Status: st, + }, nil + } + + if !s.inSharedFolder(ctx, p) && !s.inSharedFolder(ctx, dp) { + return s.move(ctx, req) + } + + // allow renaming the share folder, the mount point, not the target. + if s.isShareName(ctx, p) && s.isShareName(ctx, dp) { + log.Info().Msgf("gateway: move: renaming share mountpoint: from:%s to:%s", p, dp) + return s.move(ctx, req) + } + + // resolve references and check the ref points to the same base path, paranoia check. + if s.isShareChild(ctx, p) && s.isShareChild(ctx, dp) { + shareName, shareChild := s.splitShare(ctx, p) + dshareName, dshareChild := s.splitShare(ctx, dp) + log.Debug().Msgf("srcpath:%s dstpath:%s srcsharename:%s srcsharechild: %s dstsharename:%s dstsharechild:%s ", p, dp, shareName, shareChild, dshareName, dshareChild) + + srcStatReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} + srcStatRes, err := s.stat(ctx, srcStatReq) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+srcStatReq.Ref.String()), + }, nil + } + + if srcStatRes.Status.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: srcStatRes.Status, + }, nil + } + + dstStatReq := &provider.StatRequest{Ref: &provider.Reference{Path: dshareName}} + dstStatRes, err := s.stat(ctx, dstStatReq) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+srcStatReq.Ref.String()), + }, nil + } + + if dstStatRes.Status.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: srcStatRes.Status, + }, nil + } + + srcRi, srcProtocol, err := s.checkRef(ctx, srcStatRes.Info) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+srcStatRes.Info.Target, err), + }, nil + } + + if srcProtocol == "webdav" { + err = s.webdavRefMove(ctx, dstStatRes.Info.Target, shareChild, dshareChild) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error moving resource on webdav host: "+p), + }, nil + } + return &provider.MoveResponse{ + Status: status.NewOK(ctx), + }, nil + } + dstRi, dstProtocol, err := s.checkRef(ctx, dstStatRes.Info) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+srcStatRes.Info.Target, err), + }, nil + } + + if dstProtocol == "webdav" { + err = s.webdavRefMove(ctx, dstStatRes.Info.Target, shareChild, dshareChild) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error moving resource on webdav host: "+p), + }, nil + } + return &provider.MoveResponse{ + Status: status.NewOK(ctx), + }, nil + } + + src := &provider.Reference{ + Path: path.Join(srcRi.Path, shareChild), + } + dst := &provider.Reference{ + Path: path.Join(dstRi.Path, dshareChild), + } + + req.Source = src + req.Destination = dst + + return s.move(ctx, req) + } + + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move", errtypes.BadRequest("gateway: move called on unknown path: "+p)), + }, nil +} + +func (s *svc) move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { + srcProviders, err := s.findProviders(ctx, req.Source) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move src="+req.Source.String(), err), + }, nil + } + + dstProviders, err := s.findProviders(ctx, req.Destination) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move dst="+req.Destination.String(), err), + }, nil + } + + // if providers are not the same we do not implement cross storage move yet. + if len(srcProviders) != 1 || len(dstProviders) != 1 { + res := &provider.MoveResponse{ + Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + } + return res, nil + } + + srcProvider, dstProvider := srcProviders[0], dstProviders[0] + + // if providers are not the same we do not implement cross storage copy yet. + if srcProvider.Address != dstProvider.Address { + res := &provider.MoveResponse{ + Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + } + return res, nil + } + + c, err := s.getStorageProviderClient(ctx, srcProvider) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "error connecting to storage provider="+srcProvider.Address), + }, nil + } + + return c.Move(ctx, req) +} + +func (s *svc) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { + // TODO(ishank011): enable for references spread across storage providers, eg. /eos + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.SetArbitraryMetadataResponse{ + Status: status.NewStatusFromErrType(ctx, "SetArbitraryMetadata ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.SetArbitraryMetadata(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.SetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling SetArbitraryMetadata") + } + + return res, nil +} + +func (s *svc) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { + // TODO(ishank011): enable for references spread across storage providers, eg. /eos + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.UnsetArbitraryMetadataResponse{ + Status: status.NewStatusFromErrType(ctx, "UnsetArbitraryMetadata ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.UnsetArbitraryMetadata(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.UnsetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling UnsetArbitraryMetadata") + } + + return res, nil +} + +// SetLock puts a lock on the given reference +func (s *svc) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.SetLockResponse{ + Status: status.NewStatusFromErrType(ctx, "SetLock ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.SetLock(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.SetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling SetLock") + } + + return res, nil +} + +// GetLock returns an existing lock on the given reference +func (s *svc) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.GetLockResponse{ + Status: status.NewStatusFromErrType(ctx, "GetLock ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.GetLock(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.GetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling GetLock") + } + + return res, nil +} + +// RefreshLock refreshes an existing lock on the given reference +func (s *svc) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.RefreshLockResponse{ + Status: status.NewStatusFromErrType(ctx, "RefreshLock ref="+req.Ref.String(), err), }, nil } + res, err := c.RefreshLock(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.RefreshLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling RefreshLock") + } + + return res, nil +} + +// Unlock removes an existing lock from the given reference +func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.UnlockResponse{ + Status: status.NewStatusFromErrType(ctx, "Unlock ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.Unlock(ctx, req) + if err != nil { + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.UnlockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling Unlock") + } + + return res, nil +} + +func (s *svc) statHome(ctx context.Context) (*provider.StatResponse, error) { + statRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: s.getHome(ctx)}}) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating home"), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: statRes.Status, + }, nil + } + + statSharedFolder, err := s.statSharesFolder(ctx) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), + }, nil + } + if statSharedFolder.Status.Code != rpc.Code_CODE_OK { + // If shares folder is not found, skip updating the etag + if statSharedFolder.Status.Code == rpc.Code_CODE_NOT_FOUND { + return statRes, nil + } + // otherwise return stat of share folder + return &provider.StatResponse{ + Status: statSharedFolder.Status, + }, nil + } + + if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { + resMtime := utils.TSToTime(statRes.Info.Mtime) + resEtag := etagIface.(etagWithTS) + // Use the updated etag if the home folder has been modified + if resMtime.Before(resEtag.Timestamp) { + statRes.Info.Etag = resEtag.Etag + } + } else { + statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, []*provider.ResourceInfo{statSharedFolder.Info}) + if s.c.EtagCacheTTL > 0 { + _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) + } + } + + return statRes, nil +} + +func (s *svc) statSharesFolder(ctx context.Context) (*provider.StatResponse, error) { + statRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: statRes.Status, + }, nil + } + + lsRes, err := s.listSharesFolder(ctx) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing shares folder"), + }, nil + } + if lsRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: lsRes.Status, + }, nil + } + + if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { + resMtime := utils.TSToTime(statRes.Info.Mtime) + resEtag := etagIface.(etagWithTS) + // Use the updated etag if the shares folder has been modified, i.e., a new + // reference has been created. + if resMtime.Before(resEtag.Timestamp) { + statRes.Info.Etag = resEtag.Etag + } + } else { + statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, lsRes.Infos) + if s.c.EtagCacheTTL > 0 { + _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) + } + } + return statRes, nil +} + +func (s *svc) stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { + providers, err := s.findProviders(ctx, req.Ref) + if err != nil { + return &provider.StatResponse{ + Status: status.NewStatusFromErrType(ctx, "stat ref: "+req.Ref.String(), err), + }, nil + } + providers = getUniqueProviders(providers) + + resPath := req.Ref.GetPath() + if len(providers) == 1 && (utils.IsRelativeReference(req.Ref) || resPath == "" || strings.HasPrefix(resPath, providers[0].ProviderPath)) { + c, err := s.getStorageProviderClient(ctx, providers[0]) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "error connecting to storage provider="+providers[0].Address), + }, nil + } + rsp, err := c.Stat(ctx, req) + if err != nil || rsp.Status.Code != rpc.Code_CODE_OK { + return rsp, err + } + return rsp, nil + } + + return s.statAcrossProviders(ctx, req, providers) +} + +func (s *svc) statAcrossProviders(ctx context.Context, req *provider.StatRequest, providers []*registry.ProviderInfo) (*provider.StatResponse, error) { + // TODO(ishank011): aggregrate properties such as etag, checksum, etc. + log := appctx.GetLogger(ctx) + info := &provider.ResourceInfo{ + Id: &provider.ResourceId{ + StorageId: "/", + OpaqueId: uuid.New().String(), + }, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: req.Ref.GetPath(), + MimeType: "httpd/unix-directory", + Size: 0, + Mtime: &types.Timestamp{}, + } + + for _, p := range providers { + c, err := s.getStorageProviderClient(ctx, p) + if err != nil { + log.Err(err).Msg("error connecting to storage provider=" + p.Address) + continue + } + resp, err := c.Stat(ctx, req) + if err != nil { + log.Err(err).Msgf("gateway: error calling Stat %s: %+v", req.Ref.String(), p) + continue + } + if resp.Status.Code != rpc.Code_CODE_OK { + log.Err(status.NewErrorFromCode(rpc.Code_CODE_OK, "gateway")) + continue + } + if resp.Info != nil { + info.Size += resp.Info.Size + if utils.TSToUnixNano(resp.Info.Mtime) > utils.TSToUnixNano(info.Mtime) { + info.Mtime = resp.Info.Mtime + info.Etag = resp.Info.Etag + info.Checksum = resp.Info.Checksum + } + if info.Etag == "" && info.Etag != resp.Info.Etag { + info.Etag = resp.Info.Etag + } + } + } + + return &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: info, + }, nil +} + +func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { + if utils.IsRelativeReference(req.Ref) { + return s.stat(ctx, req) + } + + p := "" + var res *provider.StatResponse + var err error + if utils.IsAbsolutePathReference(req.Ref) { + p = req.Ref.Path + } else { + // Reference by just resource ID + // Stat it and store for future use + res, err = s.stat(ctx, req) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+req.Ref.String()), + }, nil + } + if res != nil && res.Status.Code != rpc.Code_CODE_OK { + return res, nil + } + p = res.Info.Path + } + + if path.Clean(p) == s.getHome(ctx) { + return s.statHome(ctx) + } + + if s.isSharedFolder(ctx, p) { + return s.statSharesFolder(ctx) + } + + if !s.inSharedFolder(ctx, p) { + if res != nil { + return res, nil + } + return s.stat(ctx, req) + } + + // we need to provide the info of the target, not the reference. + if s.isShareName(ctx, p) { + // If we haven't returned an error by now and res is nil, it means that + // req is an absolute path based ref, so we didn't stat it previously. + // So stat it now + if res == nil { + res, err = s.stat(ctx, req) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+req.Ref.String()), + }, nil + } + + if res.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: res.Status, + }, nil + } + } + + ri, protocol, err := s.checkRef(ctx, res.Info) + if err != nil { + return &provider.StatResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+res.Info.Target, err), + }, nil + } + + if protocol == "webdav" { + ri, err = s.webdavRefStat(ctx, res.Info.Target) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error resolving webdav reference: "+p), + }, nil + } + } + + // we need to make sure we don't expose the reference target in the resource + // information. For example, if requests comes to: /home/MyShares/photos and photos + // is reference to /user/peter/Holidays/photos, we need to still return to the user + // /home/MyShares/photos + orgPath := res.Info.Path + res.Info = ri + res.Info.Path = orgPath + return res, nil + + } + + if s.isShareChild(ctx, p) { + shareName, shareChild := s.splitShare(ctx, p) + + statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} + statRes, err := s.stat(ctx, statReq) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: statRes.Status, + }, nil + } + + ri, protocol, err := s.checkRef(ctx, statRes.Info) + if err != nil { + return &provider.StatResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), + }, nil + } + + if protocol == "webdav" { + ri, err = s.webdavRefStat(ctx, statRes.Info.Target, shareChild) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error resolving webdav reference: "+p), + }, nil + } + ri.Path = p + return &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: ri, + }, nil + } + + // append child to target + req.Ref.Path = path.Join(ri.Path, shareChild) + res, err := s.stat(ctx, req) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+req.Ref.String()), + }, nil + } + if res.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: res.Status, + }, nil + } + + // we need to make sure we don't expose the reference target in the resource + // information. + res.Info.Path = p + return res, nil + } + + panic("gateway: stating an unknown path:" + p) +} + +func (s *svc) checkRef(ctx context.Context, ri *provider.ResourceInfo) (*provider.ResourceInfo, string, error) { + if ri.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { + panic("gateway: calling checkRef on a non reference type:" + ri.String()) + } + + // reference types MUST have a target resource id. + if ri.Target == "" { + err := errtypes.BadRequest("gateway: ref target is an empty uri") + return nil, "", err + } + + uri, err := url.Parse(ri.Target) + if err != nil { + return nil, "", errors.Wrapf(err, "gateway: error parsing target uri: %s", ri.Target) + } + + switch uri.Scheme { + case "cs3": + ref, err := s.handleCS3Ref(ctx, uri.Opaque) + return ref, "cs3", err + case "webdav": + return nil, "webdav", nil + default: + err := errtypes.BadRequest("gateway: no reference handler for scheme: " + uri.Scheme) + return nil, "", err + } +} + +func (s *svc) handleCS3Ref(ctx context.Context, opaque string) (*provider.ResourceInfo, error) { + // a cs3 ref has the following layout: / + parts := strings.SplitN(opaque, "/", 2) + if len(parts) < 2 { + err := errtypes.BadRequest("gateway: cs3 ref does not follow the layout storageid/opaqueid:" + opaque) + return nil, err + } + + // we could call here the Stat method again, but that is calling for problems in case + // there is a loop of targets pointing to targets, so better avoid it. + + req := &provider.StatRequest{ + Ref: &provider.Reference{ + ResourceId: &provider.ResourceId{ + StorageId: parts[0], + OpaqueId: parts[1], + }, + }, + } + res, err := s.stat(ctx, req) + if err != nil { + return nil, errors.Wrap(err, "gateway: error calling stat") + } + + if res.Status.Code != rpc.Code_CODE_OK { + switch res.Status.Code { + case rpc.Code_CODE_NOT_FOUND: + return nil, errtypes.NotFound(req.Ref.String()) + case rpc.Code_CODE_PERMISSION_DENIED: + return nil, errtypes.PermissionDenied(req.Ref.String()) + case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_FAILED_PRECONDITION, rpc.Code_CODE_OUT_OF_RANGE: + return nil, errtypes.BadRequest(req.Ref.String()) + case rpc.Code_CODE_UNIMPLEMENTED: + return nil, errtypes.NotSupported(req.Ref.String()) + default: + return nil, errtypes.InternalError("gateway: error stating target reference") + } + } + + if res.Info.Type == provider.ResourceType_RESOURCE_TYPE_REFERENCE { + err := errtypes.BadRequest("gateway: error the target of a reference cannot be another reference") + return nil, err + } + + return res.Info, nil +} + +func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gateway.GatewayAPI_ListContainerStreamServer) error { + return errtypes.NotSupported("Unimplemented") +} + +func (s *svc) listHome(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { + lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{ + Ref: &provider.Reference{Path: s.getHome(ctx)}, + ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, + }) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing home"), + }, nil + } + if lcr.Status.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: lcr.Status, + }, nil + } + + for i := range lcr.Infos { + if s.isSharedFolder(ctx, lcr.Infos[i].GetPath()) { + statSharedFolder, err := s.statSharesFolder(ctx) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), + }, nil + } + if statSharedFolder.Status.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: statSharedFolder.Status, + }, nil + } + lcr.Infos[i] = statSharedFolder.Info + break + } + } + + return lcr, nil +} + +func (s *svc) listSharesFolder(ctx context.Context) (*provider.ListContainerResponse, error) { + lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing shared folder"), + }, nil + } + if lcr.Status.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: lcr.Status, + }, nil + } + checkedInfos := make([]*provider.ResourceInfo, 0) + for i := range lcr.Infos { + info, protocol, err := s.checkRef(ctx, lcr.Infos[i]) + if err != nil { + // create status to log the proper messages + // this might arise when the shared resource has been moved to the recycle bin + // this might arise when the resource was unshared, but the share reference was not removed + status.NewStatusFromErrType(ctx, "error resolving reference "+lcr.Infos[i].Target, err) + // continue on errors so the user can see a list of the working shares + continue + } + + if protocol == "webdav" { + info, err = s.webdavRefStat(ctx, lcr.Infos[i].Target) + if err != nil { + // Might be the case that the webdav token has expired + continue + } + } + + info.Path = lcr.Infos[i].Path + checkedInfos = append(checkedInfos, info) + } + lcr.Infos = checkedInfos + + return lcr, nil +} + +func (s *svc) isPathAllowed(ua *ua.UserAgent, path string) bool { + uaLst, ok := s.c.AllowedUserAgents[path] + if !ok { + // if no user agent is defined for a path, all user agents are allowed + return true + } + return useragent.IsUserAgentAllowed(ua, uaLst) +} + +func (s *svc) filterProvidersByUserAgent(ctx context.Context, providers []*registry.ProviderInfo) []*registry.ProviderInfo { + ua, ok := ctxpkg.ContextGetUserAgent(ctx) + if !ok { + return providers + } + + filters := []*registry.ProviderInfo{} + for _, p := range providers { + if s.isPathAllowed(ua, p.ProviderPath) { + filters = append(filters, p) + } + } + return filters +} + +func (s *svc) listContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { + providers, err := s.findProviders(ctx, req.Ref) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewStatusFromErrType(ctx, "listContainer ref: "+req.Ref.String(), err), + }, nil + } + providers = getUniqueProviders(providers) + + resPath := req.Ref.GetPath() + + if len(providers) == 1 && (utils.IsRelativeReference(req.Ref) || resPath == "" || strings.HasPrefix(resPath, providers[0].ProviderPath)) { + c, err := s.getStorageProviderClient(ctx, providers[0]) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "error connecting to storage provider="+providers[0].Address), + }, nil + } + rsp, err := c.ListContainer(ctx, req) + if err != nil || rsp.Status.Code != rpc.Code_CODE_OK { + return rsp, err + } + return rsp, nil + } + + return s.listContainerAcrossProviders(ctx, req, providers) +} + +func (s *svc) listContainerAcrossProviders(ctx context.Context, req *provider.ListContainerRequest, providers []*registry.ProviderInfo) (*provider.ListContainerResponse, error) { + nestedInfos := make(map[string]*provider.ResourceInfo) + log := appctx.GetLogger(ctx) + + for _, p := range s.filterProvidersByUserAgent(ctx, providers) { + c, err := s.getStorageProviderClient(ctx, p) + if err != nil { + log.Err(err).Msg("error connecting to storage provider=" + p.Address) + continue + } + resp, err := c.ListContainer(ctx, req) + if err != nil { + log.Err(err).Msgf("gateway: error calling Stat %s: %+v", req.Ref.String(), p) + continue + } + if resp.Status.Code != rpc.Code_CODE_OK { + log.Err(status.NewErrorFromCode(rpc.Code_CODE_OK, "gateway")) + continue + } + + for _, info := range resp.Infos { + if p, ok := nestedInfos[info.Path]; ok { + // Since more than one providers contribute to this path, + // use a generic ID + p.Id = &provider.ResourceId{ + StorageId: "/", + OpaqueId: uuid.New().String(), + } + // TODO(ishank011): aggregrate properties such as etag, checksum, etc. + p.Size += info.Size + if utils.TSToUnixNano(info.Mtime) > utils.TSToUnixNano(p.Mtime) { + p.Mtime = info.Mtime + p.Etag = info.Etag + p.Checksum = info.Checksum + } + if p.Etag == "" && p.Etag != info.Etag { + p.Etag = info.Etag + } + p.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + p.MimeType = "httpd/unix-directory" + } else { + nestedInfos[info.Path] = info + } + } + } + + infos := make([]*provider.ResourceInfo, 0, len(nestedInfos)) + for _, info := range nestedInfos { + infos = append(infos, info) + } + + return &provider.ListContainerResponse{ + Status: status.NewOK(ctx), + Infos: infos, + }, nil +} + +func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { + log := appctx.GetLogger(ctx) + + if utils.IsRelativeReference(req.Ref) { + return s.listContainer(ctx, req) + } + + p, st := s.getPath(ctx, req.Ref, req.ArbitraryMetadataKeys...) + if st.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: st, + }, nil + } + + if path.Clean(p) == s.getHome(ctx) { + return s.listHome(ctx, req) + } + + if s.isSharedFolder(ctx, p) { + return s.listSharesFolder(ctx) + } + + if !s.inSharedFolder(ctx, p) { + return s.listContainer(ctx, req) + } + + // we need to provide the info of the target, not the reference. + if s.isShareName(ctx, p) { + statReq := &provider.StatRequest{Ref: &provider.Reference{Path: p}} + statRes, err := s.stat(ctx, statReq) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating share:"+statReq.Ref.String()), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: statRes.Status, + }, nil + } + + ri, protocol, err := s.checkRef(ctx, statRes.Info) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), + }, nil + } + + if protocol == "webdav" { + infos, err := s.webdavRefLs(ctx, statRes.Info.Target) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing webdav reference: "+p), + }, nil + } + + for _, info := range infos { + base := path.Base(info.Path) + info.Path = path.Join(p, base) + } + return &provider.ListContainerResponse{ + Status: status.NewOK(ctx), + Infos: infos, + }, nil + } + + if ri.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { + err := errtypes.NotSupported("gateway: list container: cannot list non-container type:" + ri.Path) + log.Err(err).Msg("gateway: error listing") + return &provider.ListContainerResponse{ + Status: status.NewInvalidArg(ctx, "resource is not a container"), + }, nil + } + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) return res, nil } diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index cd18b3fe4a..c5bc109bb1 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -23,18 +23,17 @@ import ( "encoding/json" "path" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/share" - "github.com/cs3org/reva/v2/pkg/storage/utils/grants" - rtrace "github.com/cs3org/reva/v2/pkg/trace" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/storage/utils/grants" "github.com/pkg/errors" ) @@ -266,27 +265,37 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update }, nil } - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Share.Share.ResourceId) - return c.UpdateReceivedShare(ctx, req) - /* - TODO: Leftover from master merge. Do we need this? - if err != nil { - appctx.GetLogger(ctx). - Err(err). - Msg("UpdateReceivedShare: failed to get user share provider") - return &collaboration.UpdateReceivedShareResponse{ - Status: status.NewInternal(ctx, "error getting share provider client"), - }, nil - } - // check if we have a resource id in the update response that we can use to update references - if res.GetShare().GetShare().GetResourceId() == nil { - log.Err(err).Msg("gateway: UpdateReceivedShare must return a ResourceId") - return &collaboration.UpdateReceivedShareResponse{ - Status: &rpc.Status{ - Code: rpc.Code_CODE_INTERNAL, - }, - }, nil - } + res, err := c.UpdateReceivedShare(ctx, req) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + return &collaboration.UpdateReceivedShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + + // error failing to update share state. + if res.Status.Code != rpc.Code_CODE_OK { + return res, nil + } + + // if we don't need to create/delete references then we return early. + if !s.c.CommitShareToStorageRef || + ctxpkg.ContextMustGetUser(ctx).Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || + ctxpkg.ContextMustGetUser(ctx).Id.Type == userpb.UserType_USER_TYPE_FEDERATED { + return res, nil + } + + // check if we have a resource id in the update response that we can use to update references + if res.GetShare().GetShare().GetResourceId() == nil { + log.Err(err).Msg("gateway: UpdateReceivedShare must return a ResourceId") + return &collaboration.UpdateReceivedShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } // properties are updated in the order they appear in the field mask // when an error occurs the request ends and no further fields are updated diff --git a/internal/grpc/services/groupprovider/groupprovider.go b/internal/grpc/services/groupprovider/groupprovider.go index c7463e86a3..cd960ab78a 100644 --- a/internal/grpc/services/groupprovider/groupprovider.go +++ b/internal/grpc/services/groupprovider/groupprovider.go @@ -101,7 +101,7 @@ func (s *service) Register(ss *grpc.Server) { } func (s *service) GetGroup(ctx context.Context, req *grouppb.GetGroupRequest) (*grouppb.GetGroupResponse, error) { - group, err := s.groupmgr.GetGroup(ctx, req.GroupId) + group, err := s.groupmgr.GetGroup(ctx, req.GroupId, req.SkipFetchingMembers) if err != nil { res := &grouppb.GetGroupResponse{} if _, ok := err.(errtypes.NotFound); ok { @@ -119,7 +119,7 @@ func (s *service) GetGroup(ctx context.Context, req *grouppb.GetGroupRequest) (* } func (s *service) GetGroupByClaim(ctx context.Context, req *grouppb.GetGroupByClaimRequest) (*grouppb.GetGroupByClaimResponse, error) { - group, err := s.groupmgr.GetGroupByClaim(ctx, req.Claim, req.Value) + group, err := s.groupmgr.GetGroupByClaim(ctx, req.Claim, req.Value, req.SkipFetchingMembers) if err != nil { res := &grouppb.GetGroupByClaimResponse{} if _, ok := err.(errtypes.NotFound); ok { @@ -137,7 +137,7 @@ func (s *service) GetGroupByClaim(ctx context.Context, req *grouppb.GetGroupByCl } func (s *service) FindGroups(ctx context.Context, req *grouppb.FindGroupsRequest) (*grouppb.FindGroupsResponse, error) { - groups, err := s.groupmgr.FindGroups(ctx, req.Filter) + groups, err := s.groupmgr.FindGroups(ctx, req.Filter, req.SkipFetchingMembers) if err != nil { return &grouppb.FindGroupsResponse{ Status: status.NewInternal(ctx, "error finding groups"), diff --git a/internal/grpc/services/loader/loader.go b/internal/grpc/services/loader/loader.go index 825d9ea6ad..e0161997d2 100644 --- a/internal/grpc/services/loader/loader.go +++ b/internal/grpc/services/loader/loader.go @@ -20,27 +20,26 @@ package loader import ( // Load core gRPC services. - _ "github.com/cs3org/reva/v2/internal/grpc/services/applicationauth" - _ "github.com/cs3org/reva/v2/internal/grpc/services/appprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/appregistry" - _ "github.com/cs3org/reva/v2/internal/grpc/services/authprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/authregistry" - _ "github.com/cs3org/reva/v2/internal/grpc/services/datatx" - _ "github.com/cs3org/reva/v2/internal/grpc/services/gateway" - _ "github.com/cs3org/reva/v2/internal/grpc/services/groupprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/helloworld" - _ "github.com/cs3org/reva/v2/internal/grpc/services/ocmcore" - _ "github.com/cs3org/reva/v2/internal/grpc/services/ocminvitemanager" - _ "github.com/cs3org/reva/v2/internal/grpc/services/ocmproviderauthorizer" - _ "github.com/cs3org/reva/v2/internal/grpc/services/ocmshareprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/permissions" - _ "github.com/cs3org/reva/v2/internal/grpc/services/preferences" - _ "github.com/cs3org/reva/v2/internal/grpc/services/publicshareprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/publicstorageprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/sharesstorageprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/storageprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/storageregistry" - _ "github.com/cs3org/reva/v2/internal/grpc/services/userprovider" - _ "github.com/cs3org/reva/v2/internal/grpc/services/usershareprovider" + _ "github.com/cs3org/reva/internal/grpc/services/applicationauth" + _ "github.com/cs3org/reva/internal/grpc/services/appprovider" + _ "github.com/cs3org/reva/internal/grpc/services/appregistry" + _ "github.com/cs3org/reva/internal/grpc/services/authprovider" + _ "github.com/cs3org/reva/internal/grpc/services/authregistry" + _ "github.com/cs3org/reva/internal/grpc/services/datatx" + _ "github.com/cs3org/reva/internal/grpc/services/gateway" + _ "github.com/cs3org/reva/internal/grpc/services/groupprovider" + _ "github.com/cs3org/reva/internal/grpc/services/helloworld" + _ "github.com/cs3org/reva/internal/grpc/services/ocmcore" + _ "github.com/cs3org/reva/internal/grpc/services/ocminvitemanager" + _ "github.com/cs3org/reva/internal/grpc/services/ocmproviderauthorizer" + _ "github.com/cs3org/reva/internal/grpc/services/ocmshareprovider" + _ "github.com/cs3org/reva/internal/grpc/services/permissions" + _ "github.com/cs3org/reva/internal/grpc/services/preferences" + _ "github.com/cs3org/reva/internal/grpc/services/publicshareprovider" + _ "github.com/cs3org/reva/internal/grpc/services/publicstorageprovider" + _ "github.com/cs3org/reva/internal/grpc/services/storageprovider" + _ "github.com/cs3org/reva/internal/grpc/services/storageregistry" + _ "github.com/cs3org/reva/internal/grpc/services/userprovider" + _ "github.com/cs3org/reva/internal/grpc/services/usershareprovider" // Add your own service here ) diff --git a/internal/grpc/services/ocmcore/ocmcore.go b/internal/grpc/services/ocmcore/ocmcore.go index 9782688f3d..745306fd41 100644 --- a/internal/grpc/services/ocmcore/ocmcore.go +++ b/internal/grpc/services/ocmcore/ocmcore.go @@ -27,11 +27,11 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/ocm/share" - "github.com/cs3org/reva/v2/pkg/ocm/share/manager/registry" - "github.com/cs3org/reva/v2/pkg/rgrpc" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/ocm/share" + "github.com/cs3org/reva/pkg/ocm/share/manager/registry" + "github.com/cs3org/reva/pkg/rgrpc" + "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/grpc" diff --git a/internal/grpc/services/permissions/permissions.go b/internal/grpc/services/permissions/permissions.go index 1312a7f750..4479fdb88c 100644 --- a/internal/grpc/services/permissions/permissions.go +++ b/internal/grpc/services/permissions/permissions.go @@ -24,9 +24,9 @@ import ( permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/permission" - "github.com/cs3org/reva/v2/pkg/permission/manager/registry" - "github.com/cs3org/reva/v2/pkg/rgrpc" + "github.com/cs3org/reva/pkg/permission" + "github.com/cs3org/reva/pkg/permission/manager/registry" + "github.com/cs3org/reva/pkg/rgrpc" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/grpc" diff --git a/internal/grpc/services/preferences/preferences.go b/internal/grpc/services/preferences/preferences.go index 26ed1aceb0..6920fc7069 100644 --- a/internal/grpc/services/preferences/preferences.go +++ b/internal/grpc/services/preferences/preferences.go @@ -20,38 +20,73 @@ package preferences import ( "context" - "sync" "google.golang.org/grpc" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/rgrpc" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" + preferencespb "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" + "github.com/cs3org/reva/pkg/rgrpc" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) -type contextUserRequiredErr string - -func (err contextUserRequiredErr) Error() string { return string(err) } - func init() { rgrpc.Register("preferences", New) } -// m maps user to map of user preferences. -// m = map[userToken]map[key]value -var m = make(map[string]map[string]string) +type config struct { + Driver string `mapstructure:"driver"` + Drivers map[string]map[string]interface{} `mapstructure:"drivers"` +} + +func (c *config) init() { + if c.Driver == "" { + c.Driver = "memory" + } +} -var mutex = &sync.Mutex{} +type service struct { + conf *config + pm preferences.Manager +} -type service struct{} +func getPreferencesManager(c *config) (preferences.Manager, error) { + if f, ok := registry.NewFuncs[c.Driver]; ok { + return f(c.Drivers[c.Driver]) + } + return nil, errtypes.NotFound("driver not found: " + c.Driver) +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} // New returns a new PreferencesServiceServer func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { - service := &service{} - return service, nil + c, err := parseConfig(m) + if err != nil { + return nil, err + } + + c.init() + + pm, err := getPreferencesManager(c) + if err != nil { + return nil, err + } + + return &service{ + conf: c, + pm: pm, + }, nil } func (s *service) Close() error { @@ -63,72 +98,36 @@ func (s *service) UnprotectedEndpoints() []string { } func (s *service) Register(ss *grpc.Server) { - preferences.RegisterPreferencesAPIServer(ss, s) + preferencespb.RegisterPreferencesAPIServer(ss, s) } -func getUser(ctx context.Context) (*userpb.User, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(contextUserRequiredErr("userrequired"), "preferences: error getting user from ctx") - return nil, err - } - return u, nil -} - -func (s *service) SetKey(ctx context.Context, req *preferences.SetKeyRequest) (*preferences.SetKeyResponse, error) { - key := req.Key - value := req.Val - - u, err := getUser(ctx) +func (s *service) SetKey(ctx context.Context, req *preferencespb.SetKeyRequest) (*preferencespb.SetKeyResponse, error) { + err := s.pm.SetKey(ctx, req.Key.Key, req.Key.Namespace, req.Val) if err != nil { - err = errors.Wrap(err, "preferences: failed to call getUser") - return &preferences.SetKeyResponse{ - Status: status.NewUnauthenticated(ctx, err, "user not found or invalid"), - }, err + return &preferencespb.SetKeyResponse{ + Status: status.NewInternal(ctx, err, "error setting key"), + }, nil } - name := u.Username - - mutex.Lock() - defer mutex.Unlock() - if len(m[name]) == 0 { - m[name] = map[string]string{key.Key: value} - } else { - usersettings := m[name] - usersettings[key.Key] = value - } - - return &preferences.SetKeyResponse{ + return &preferencespb.SetKeyResponse{ Status: status.NewOK(ctx), }, nil } -func (s *service) GetKey(ctx context.Context, req *preferences.GetKeyRequest) (*preferences.GetKeyResponse, error) { - key := req.Key - u, err := getUser(ctx) +func (s *service) GetKey(ctx context.Context, req *preferencespb.GetKeyRequest) (*preferencespb.GetKeyResponse, error) { + val, err := s.pm.GetKey(ctx, req.Key.Key, req.Key.Namespace) if err != nil { - err = errors.Wrap(err, "preferences: failed to call getUser") - return &preferences.GetKeyResponse{ - Status: status.NewUnauthenticated(ctx, err, "user not found or invalid"), - }, err - } - - name := u.Username - - mutex.Lock() - defer mutex.Unlock() - if len(m[name]) != 0 { - if value, ok := m[name][key.Key]; ok { - return &preferences.GetKeyResponse{ - Status: status.NewOK(ctx), - Val: value, - }, nil + st := status.NewInternal(ctx, err, "error retrieving key") + if _, ok := err.(errtypes.IsNotFound); ok { + st = status.NewNotFound(ctx, "key not found") } + return &preferencespb.GetKeyResponse{ + Status: st, + }, nil } - res := &preferences.GetKeyResponse{ - Status: status.NewNotFound(ctx, "key not found"), - Val: "", - } - return res, nil + return &preferencespb.GetKeyResponse{ + Status: status.NewOK(ctx), + Val: val, + }, nil } diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 4bbed7f24f..a1394a05c9 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -120,58 +120,22 @@ func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.Unse // SetLock puts a lock on the given reference func (s *service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) { - ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - switch { - case err != nil: - return nil, err - case st != nil: - return &provider.SetLockResponse{ - Status: st, - }, nil - } - return s.gateway.SetLock(ctx, &provider.SetLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } // GetLock returns an existing lock on the given reference func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) { - ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - switch { - case err != nil: - return nil, err - case st != nil: - return &provider.GetLockResponse{ - Status: st, - }, nil - } - return s.gateway.GetLock(ctx, &provider.GetLockRequest{Opaque: req.Opaque, Ref: ref}) + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } // RefreshLock refreshes an existing lock on the given reference func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { - ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - switch { - case err != nil: - return nil, err - case st != nil: - return &provider.RefreshLockResponse{ - Status: st, - }, nil - } - return s.gateway.RefreshLock(ctx, &provider.RefreshLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } // Unlock removes an existing lock from the given reference func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { - ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) - switch { - case err != nil: - return nil, err - case st != nil: - return &provider.UnlockResponse{ - Status: st, - }, nil - } - return s.gateway.Unlock(ctx, &provider.UnlockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock}) + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { @@ -215,23 +179,11 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. return nil, "", nil, st, nil } - var path string - switch shareInfo.Type { - case provider.ResourceType_RESOURCE_TYPE_CONTAINER: - // folders point to the folder -> path needs to be added - path = utils.MakeRelativePath(ref.Path) - case provider.ResourceType_RESOURCE_TYPE_FILE: - // files already point to the correct id - path = "." - default: - // TODO: can this happen? - // path = utils.MakeRelativePath(relativePath) - } - - cs3Ref := &provider.Reference{ - ResourceId: shareInfo.Id, - Path: path, + p := shareInfo.Path + if shareInfo.Type != provider.ResourceType_RESOURCE_TYPE_FILE { + p = path.Join("/", shareInfo.Path, relativePath) } + cs3Ref := &provider.Reference{Path: p} log.Debug(). Interface("sourceRef", ref). @@ -755,8 +707,10 @@ func (s *service) augmentStatResponse(ctx context.Context, res *provider.StatRes // setPublicStorageID encodes the actual spaceid and nodeid as an opaqueid in the publicstorageprovider space func (s *service) setPublicStorageID(info *provider.ResourceInfo, shareToken string) { - info.Id.StorageId = utils.PublicStorageProviderID - info.Id.OpaqueId = shareToken + if s.mountID != "" { + info.Id.StorageId = s.mountID + info.Id.OpaqueId = shareToken + "/" + info.Id.OpaqueId + } } func addShare(i *provider.ResourceInfo, ls *link.PublicShare) error { diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index da3f291e18..755ddcab3e 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -21,6 +21,7 @@ package storageprovider import ( "context" "fmt" + "io/ioutil" "net/url" "os" "path" @@ -52,13 +53,15 @@ func init() { } type config struct { - Driver string `mapstructure:"driver" docs:"localhome;The storage driver to be used."` - Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/storage/fs/localhome/localhome.go"` - TmpFolder string `mapstructure:"tmp_folder" docs:"/var/tmp;Path to temporary folder."` - DataServerURL string `mapstructure:"data_server_url" docs:"http://localhost/data;The URL for the data server."` - ExposeDataServer bool `mapstructure:"expose_data_server" docs:"false;Whether to expose data server."` // if true the client will be able to upload/download directly to it - AvailableXS map[string]uint32 `mapstructure:"available_checksums" docs:"nil;List of available checksums."` - MimeTypes map[string]string `mapstructure:"mimetypes" docs:"nil;List of supported mime types and corresponding file extensions."` + MountPath string `mapstructure:"mount_path" docs:"/;The path where the file system would be mounted."` + MountID string `mapstructure:"mount_id" docs:"-;The ID of the mounted file system."` + Driver string `mapstructure:"driver" docs:"localhome;The storage driver to be used."` + Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/storage/fs/localhome/localhome.go"` + TmpFolder string `mapstructure:"tmp_folder" docs:"/var/tmp;Path to temporary folder."` + DataServerURL string `mapstructure:"data_server_url" docs:"http://localhost/data;The URL for the data server."` + ExposeDataServer bool `mapstructure:"expose_data_server" docs:"false;Whether to expose data server."` // if true the client will be able to upload/download directly to it + AvailableXS map[string]uint32 `mapstructure:"available_checksums" docs:"nil;List of available checksums."` + CustomMimeTypesJSON string `mapstructure:"custom_mimetypes_json" docs:"nil;An optional mapping file with the list of supported custom file extensions and corresponding mime types."` } func (c *config) init() { @@ -83,10 +86,6 @@ func (c *config) init() { if len(c.AvailableXS) == 0 { c.AvailableXS = map[string]uint32{"md5": 100, "unset": 1000} } - if c.MimeTypes == nil || len(c.MimeTypes) == 0 { - c.MimeTypes = map[string]string{".zmd": "application/compressed-markdown"} - } - } type service struct { @@ -132,6 +131,25 @@ func parseConfig(m map[string]interface{}) (*config, error) { return c, nil } +func registerMimeTypes(mappingFile string) error { + if mappingFile != "" { + f, err := ioutil.ReadFile(mappingFile) + if err != nil { + return fmt.Errorf("storageprovider: error reading the custom mime types file: +%v", err) + } + mimeTypes := map[string]string{} + err = json.Unmarshal(f, &mimeTypes) + if err != nil { + return fmt.Errorf("storageprovider: error unmarshalling the custom mime types file: +%v", err) + } + // register all mime types that were read + for e, m := range mimeTypes { + mime.RegisterMime(e, m) + } + } + return nil +} + // New creates a new storage provider svc func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { @@ -167,7 +185,11 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { return nil, errtypes.NotFound("no available checksum, please set in config") } - registerMimeTypes(c.MimeTypes) + // read and register custom mime types if configured + err = registerMimeTypes(c.CustomMimeTypesJSON) + if err != nil { + return nil, err + } service := &service{ conf: c, @@ -180,12 +202,6 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { return service, nil } -func registerMimeTypes(mimes map[string]string) { - for k, v := range mimes { - mime.RegisterMime(k, v) - } -} - func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { ctx = ctxpkg.ContextSetLockID(ctx, req.LockId) @@ -243,6 +259,132 @@ func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*pro }, nil } +// SetLock puts a lock on the given reference +func (s *service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + err := errors.Wrap(err, "storageprovidersvc: error unwrapping path") + return &provider.SetLockResponse{ + Status: status.NewInternal(ctx, err, "error setting lock"), + }, nil + } + + if err := s.storage.SetLock(ctx, newRef, req.Lock); err != nil { + var st *rpc.Status + switch err.(type) { + case errtypes.IsNotFound: + st = status.NewNotFound(ctx, "path not found when setting lock") + case errtypes.PermissionDenied: + st = status.NewPermissionDenied(ctx, err, "permission denied") + default: + st = status.NewInternal(ctx, err, "error setting lock: "+req.Ref.String()) + } + return &provider.SetLockResponse{ + Status: st, + }, nil + } + + res := &provider.SetLockResponse{ + Status: status.NewOK(ctx), + } + return res, nil +} + +// GetLock returns an existing lock on the given reference +func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + err := errors.Wrap(err, "storageprovidersvc: error unwrapping path") + return &provider.GetLockResponse{ + Status: status.NewInternal(ctx, err, "error getting lock"), + }, nil + } + + var lock *provider.Lock + if lock, err = s.storage.GetLock(ctx, newRef); err != nil { + var st *rpc.Status + switch err.(type) { + case errtypes.IsNotFound: + st = status.NewNotFound(ctx, "path not found when getting lock") + case errtypes.PermissionDenied: + st = status.NewPermissionDenied(ctx, err, "permission denied") + default: + st = status.NewInternal(ctx, err, "error getting lock: "+req.Ref.String()) + } + return &provider.GetLockResponse{ + Status: st, + }, nil + } + + res := &provider.GetLockResponse{ + Status: status.NewOK(ctx), + Lock: lock, + } + return res, nil +} + +// RefreshLock refreshes an existing lock on the given reference +func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + err := errors.Wrap(err, "storageprovidersvc: error unwrapping path") + return &provider.RefreshLockResponse{ + Status: status.NewInternal(ctx, err, "error refreshing lock"), + }, nil + } + + if err = s.storage.RefreshLock(ctx, newRef, req.Lock); err != nil { + var st *rpc.Status + switch err.(type) { + case errtypes.IsNotFound: + st = status.NewNotFound(ctx, "path not found when refreshing lock") + case errtypes.PermissionDenied: + st = status.NewPermissionDenied(ctx, err, "permission denied") + default: + st = status.NewInternal(ctx, err, "error refreshing lock: "+req.Ref.String()) + } + return &provider.RefreshLockResponse{ + Status: st, + }, nil + } + + res := &provider.RefreshLockResponse{ + Status: status.NewOK(ctx), + } + return res, nil +} + +// Unlock removes an existing lock from the given reference +func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + err := errors.Wrap(err, "storageprovidersvc: error unwrapping path") + return &provider.UnlockResponse{ + Status: status.NewInternal(ctx, err, "error on unlocking"), + }, nil + } + + if err = s.storage.Unlock(ctx, newRef); err != nil { + var st *rpc.Status + switch err.(type) { + case errtypes.IsNotFound: + st = status.NewNotFound(ctx, "path not found when unlocking") + case errtypes.PermissionDenied: + st = status.NewPermissionDenied(ctx, err, "permission denied") + default: + st = status.NewInternal(ctx, err, "error unlocking: "+req.Ref.String()) + } + return &provider.UnlockResponse{ + Status: st, + }, nil + } + + res := &provider.UnlockResponse{ + Status: status.NewOK(ctx), + } + return res, nil +} + func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { // TODO(labkode): maybe add some checks before download starts? eg. check permissions? // TODO(labkode): maybe add short-lived token? @@ -537,27 +679,42 @@ func (s *service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteSt return res, nil } -func (s *service) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { - // FIXME these should be part of the CreateContainerRequest object - if req.Opaque != nil { - if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { - ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) +func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + return &provider.TouchFileResponse{ + Status: status.NewInternal(ctx, err, "error unwrapping path"), + }, nil + } + if err := s.storage.TouchFile(ctx, newRef); err != nil { + var st *rpc.Status + switch err.(type) { + case errtypes.IsNotFound: + st = status.NewNotFound(ctx, "path not found when touching the file") + case errtypes.AlreadyExists: + st = status.NewAlreadyExists(ctx, err, "file already exists") + case errtypes.PermissionDenied: + st = status.NewPermissionDenied(ctx, err, "permission denied") + default: + st = status.NewInternal(ctx, err, "error touching file: "+req.Ref.String()) } + return &provider.TouchFileResponse{ + Status: st, + }, nil } - err := s.storage.CreateDir(ctx, req.Ref) - - return &provider.CreateContainerResponse{ - Status: status.NewStatusFromErrType(ctx, "create container", err), - }, nil + res := &provider.TouchFileResponse{ + Status: status.NewOK(ctx), + } + return res, nil } -func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { - // FIXME these should be part of the TouchFileRequest object - if req.Opaque != nil { - if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { - ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) - } +func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "error unwrapping path"), + }, nil } err := s.storage.TouchFile(ctx, req.Ref) @@ -765,6 +922,15 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ }, nil } + prefixMountpoint := utils.IsAbsoluteReference(req.Ref) + for _, md := range items { + if err := s.wrapReference(ctx, md.Ref, prefixMountpoint); err != nil { + return &provider.ListRecycleResponse{ + Status: status.NewInternal(ctx, err, "error wrapping path"), + }, nil + } + } + res := &provider.ListRecycleResponse{ Status: status.NewOK(ctx), RecycleItems: items, @@ -1055,6 +1221,59 @@ func getFS(c *config) (storage.FS, error) { return nil, errtypes.NotFound("driver not found: " + c.Driver) } +func (s *service) unwrap(ctx context.Context, ref *provider.Reference) (*provider.Reference, error) { + // all references with an id can be passed on to the driver + // there are two cases: + // 1. absolute id references (resource_id is set, path is empty) + // 2. relative references (resource_id is set, path starts with a `.`) + if ref.GetResourceId() != nil { + return ref, nil + } + + if !strings.HasPrefix(ref.GetPath(), "/") { + // abort, absolute path references must start with a `/` + return nil, errtypes.BadRequest("ref is invalid: " + ref.String()) + } + + // TODO move mount path trimming to the gateway + fn, err := s.trimMountPrefix(ref.GetPath()) + if err != nil { + return nil, err + } + return &provider.Reference{Path: fn}, nil +} + +func (s *service) trimMountPrefix(fn string) (string, error) { + if strings.HasPrefix(fn, s.mountPath) { + return path.Join("/", strings.TrimPrefix(fn, s.mountPath)), nil + } + return "", errtypes.BadRequest(fmt.Sprintf("path=%q does not belong to this storage provider mount path=%q", fn, s.mountPath)) +} + +func (s *service) wrap(ctx context.Context, ri *provider.ResourceInfo, prefixMountpoint bool) error { + if ri.Id.StorageId == "" { + // For wrapper drivers, the storage ID might already be set. In that case, skip setting it + ri.Id.StorageId = s.mountID + } + if prefixMountpoint { + // TODO move mount path prefixing to the gateway + ri.Path = path.Join(s.mountPath, ri.Path) + } + return nil +} + +func (s *service) wrapReference(ctx context.Context, ref *provider.Reference, prefixMountpoint bool) error { + if ref.ResourceId != nil && ref.ResourceId.StorageId == "" { + // For wrapper drivers, the storage ID might already be set. In that case, skip setting it + ref.ResourceId.StorageId = s.mountID + } + if prefixMountpoint { + // TODO move mount path prefixing to the gateway + ref.Path = path.Join(s.mountPath, ref.Path) + } + return nil +} + type descendingMtime []*provider.FileVersion func (v descendingMtime) Len() int { diff --git a/internal/grpc/services/userprovider/userprovider.go b/internal/grpc/services/userprovider/userprovider.go index 097aad98f4..bf061be0db 100644 --- a/internal/grpc/services/userprovider/userprovider.go +++ b/internal/grpc/services/userprovider/userprovider.go @@ -125,7 +125,7 @@ func (s *service) Register(ss *grpc.Server) { } func (s *service) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.GetUserResponse, error) { - user, err := s.usermgr.GetUser(ctx, req.UserId) + user, err := s.usermgr.GetUser(ctx, req.UserId, req.SkipFetchingUserGroups) if err != nil { res := &userpb.GetUserResponse{} if _, ok := err.(errtypes.NotFound); ok { @@ -144,7 +144,7 @@ func (s *service) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*use } func (s *service) GetUserByClaim(ctx context.Context, req *userpb.GetUserByClaimRequest) (*userpb.GetUserByClaimResponse, error) { - user, err := s.usermgr.GetUserByClaim(ctx, req.Claim, req.Value) + user, err := s.usermgr.GetUserByClaim(ctx, req.Claim, req.Value, req.SkipFetchingUserGroups) if err != nil { res := &userpb.GetUserByClaimResponse{} if _, ok := err.(errtypes.NotFound); ok { @@ -163,7 +163,7 @@ func (s *service) GetUserByClaim(ctx context.Context, req *userpb.GetUserByClaim } func (s *service) FindUsers(ctx context.Context, req *userpb.FindUsersRequest) (*userpb.FindUsersResponse, error) { - users, err := s.usermgr.FindUsers(ctx, req.Filter) + users, err := s.usermgr.FindUsers(ctx, req.Filter, req.SkipFetchingUserGroups) if err != nil { res := &userpb.FindUsersResponse{ Status: status.NewInternal(ctx, "error finding users"), diff --git a/internal/http/interceptors/auth/auth.go b/internal/http/interceptors/auth/auth.go index 85fb88b86f..c73b8afacb 100644 --- a/internal/http/interceptors/auth/auth.go +++ b/internal/http/interceptors/auth/auth.go @@ -19,6 +19,7 @@ package auth import ( + "context" "fmt" "net/http" "strings" @@ -28,21 +29,24 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/registry" - tokenregistry "github.com/cs3org/reva/v2/internal/http/interceptors/auth/token/registry" - tokenwriterregistry "github.com/cs3org/reva/v2/internal/http/interceptors/auth/tokenwriter/registry" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/auth" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/rhttp/global" - "github.com/cs3org/reva/v2/pkg/sharedconf" - tokenmgr "github.com/cs3org/reva/v2/pkg/token/manager/registry" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/internal/http/interceptors/auth/credential/registry" + tokenregistry "github.com/cs3org/reva/internal/http/interceptors/auth/token/registry" + tokenwriterregistry "github.com/cs3org/reva/internal/http/interceptors/auth/tokenwriter/registry" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/token" + tokenmgr "github.com/cs3org/reva/pkg/token/manager/registry" + "github.com/cs3org/reva/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" + "github.com/rs/zerolog" "google.golang.org/grpc/metadata" ) @@ -151,7 +155,6 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err chain := func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() // OPTION requests need to pass for preflight requests // TODO(labkode): this will break options for auth protected routes. // Maybe running the CORS middleware before auth kicks in is enough. @@ -160,138 +163,156 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err return } - log := appctx.GetLogger(ctx) + log := appctx.GetLogger(r.Context()) + isUnprotectedEndpoint := false - client, err := pool.GetGatewayServiceClient(conf.GatewaySvc) - if err != nil { - log.Error().Err(err).Msg("error getting the authsvc client") - w.WriteHeader(http.StatusUnauthorized) - return - } - - // skip auth for urls set in the config. - // TODO(labkode): maybe use method:url to bypass auth. + // For unprotected URLs, we try to authenticate the request in case some service needs it, + // but don't return any errors if it fails. if utils.Skip(r.URL.Path, unprotected) { log.Info().Msg("skipping auth check for: " + r.URL.Path) - h.ServeHTTP(w, r) - return + isUnprotectedEndpoint = true } - tkn := tokenStrategy.GetToken(r) - if tkn == "" { - log.Warn().Msg("core access token not set") - - userAgentCredKeys := getCredsForUserAgent(r.UserAgent(), conf.CredentialsByUserAgent, conf.CredentialChain) - - // obtain credentials (basic auth, bearer token, ...) based on user agent - var creds *auth.Credentials - for _, k := range userAgentCredKeys { - creds, err = credChain[k].GetCredentials(w, r) - if err != nil { - log.Debug().Err(err).Msg("error retrieving credentials") - } - - if creds != nil { - log.Debug().Msgf("credentials obtained from credential strategy: type: %s, client_id: %s", creds.Type, creds.ClientID) - break - } - } - - // if no credentials are found, reply with authentication challenge depending on user agent - if creds == nil { - for _, key := range userAgentCredKeys { - if cred, ok := credChain[key]; ok { - cred.AddWWWAuthenticate(w, r, conf.Realm) - } else { - panic("auth credential strategy: " + key + "must have been loaded in init method") - } - } - w.WriteHeader(http.StatusUnauthorized) + ctx, err := authenticateUser(w, r, conf, unprotected, tokenStrategy, tokenManager, tokenWriter, credChain, isUnprotectedEndpoint) + if err != nil { + if !isUnprotectedEndpoint { return } + } else { + r = r.WithContext(ctx) + } + h.ServeHTTP(w, r) + }) + } + return chain, nil +} - req := &gateway.AuthenticateRequest{ - Type: creds.Type, - ClientId: creds.ClientID, - ClientSecret: creds.ClientSecret, - } +func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, unprotected []string, tokenStrategy auth.TokenStrategy, tokenManager token.Manager, tokenWriter auth.TokenWriter, credChain map[string]auth.CredentialStrategy, isUnprotectedEndpoint bool) (context.Context, error) { + ctx := r.Context() + log := appctx.GetLogger(ctx) - log.Debug().Msgf("AuthenticateRequest: type: %s, client_id: %s against %s", req.Type, req.ClientId, conf.GatewaySvc) + // Add the request user-agent to the ctx + ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{ctxpkg.UserAgentHeader: r.UserAgent()})) - res, err := client.Authenticate(ctx, req) - if err != nil { - log.Error().Err(err).Msg("error calling Authenticate") - w.WriteHeader(http.StatusUnauthorized) - return - } + client, err := pool.GetGatewayServiceClient(conf.GatewaySvc) + if err != nil { + logError(isUnprotectedEndpoint, log, err, "error getting the authsvc client", http.StatusUnauthorized, w) + return nil, err + } - if res.Status.Code != rpc.Code_CODE_OK { - err := status.NewErrorFromCode(res.Status.Code, "auth") - log.Err(err).Msg("error generating access token from credentials") - w.WriteHeader(http.StatusUnauthorized) - return - } + tkn := tokenStrategy.GetToken(r) + if tkn == "" { + log.Warn().Msg("core access token not set") - log.Info().Msg("core access token generated") - // write token to response - tkn = res.Token - tokenWriter.WriteToken(tkn, w) - } else { - log.Debug().Msg("access token is already provided") - } + userAgentCredKeys := getCredsForUserAgent(r.UserAgent(), conf.CredentialsByUserAgent, conf.CredentialChain) - // validate token - u, tokenScope, err := tokenManager.DismantleToken(r.Context(), tkn) + // obtain credentials (basic auth, bearer token, ...) based on user agent + var creds *auth.Credentials + for _, k := range userAgentCredKeys { + creds, err = credChain[k].GetCredentials(w, r) if err != nil { - log.Error().Err(err).Msg("error dismantling token") - w.WriteHeader(http.StatusUnauthorized) - return + log.Debug().Err(err).Msg("error retrieving credentials") + } + + if creds != nil { + log.Debug().Msgf("credentials obtained from credential strategy: type: %s, client_id: %s", creds.Type, creds.ClientID) + break } + } - if sharedconf.SkipUserGroupsInToken() { - var groups []string - if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil { - groups = groupsIf.([]string) - } else { - groupsRes, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id}) - if err != nil { - log.Error().Err(err).Msg("error retrieving user groups") - w.WriteHeader(http.StatusInternalServerError) - return + // if no credentials are found, reply with authentication challenge depending on user agent + if creds == nil { + if !isUnprotectedEndpoint { + for _, key := range userAgentCredKeys { + if cred, ok := credChain[key]; ok { + cred.AddWWWAuthenticate(w, r, conf.Realm) + } else { + panic("auth credential strategy: " + key + "must have been loaded in init method") } - groups = groupsRes.Groups - _ = userGroupsCache.SetWithExpire(u.Id.OpaqueId, groupsRes.Groups, 3600*time.Second) } - u.Groups = groups + w.WriteHeader(http.StatusUnauthorized) } + return nil, errtypes.PermissionDenied("no credentials found") + } + + req := &gateway.AuthenticateRequest{ + Type: creds.Type, + ClientId: creds.ClientID, + ClientSecret: creds.ClientSecret, + } + + log.Debug().Msgf("AuthenticateRequest: type: %s, client_id: %s against %s", req.Type, req.ClientId, conf.GatewaySvc) + + res, err := client.Authenticate(ctx, req) + if err != nil { + logError(isUnprotectedEndpoint, log, err, "error calling Authenticate", http.StatusUnauthorized, w) + return nil, err + } + + if res.Status.Code != rpc.Code_CODE_OK { + err := status.NewErrorFromCode(res.Status.Code, "auth") + logError(isUnprotectedEndpoint, log, err, "error generating access token from credentials", http.StatusUnauthorized, w) + return nil, err + } + + log.Info().Msg("core access token generated") + // write token to response + tkn = res.Token + tokenWriter.WriteToken(tkn, w) + } else { + log.Debug().Msg("access token is already provided") + } + + // validate token + u, tokenScope, err := tokenManager.DismantleToken(r.Context(), tkn) + if err != nil { + logError(isUnprotectedEndpoint, log, err, "error dismantling token", http.StatusUnauthorized, w) + return nil, err + } - // ensure access to the resource is allowed - ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path) + if sharedconf.SkipUserGroupsInToken() { + var groups []string + if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil { + groups = groupsIf.([]string) + } else { + groupsRes, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id}) if err != nil { - log.Error().Err(err).Msg("error verifying scope of access token") - w.WriteHeader(http.StatusInternalServerError) - } - if !ok { - log.Error().Err(err).Msg("access to resource not allowed") - w.WriteHeader(http.StatusUnauthorized) - return + logError(isUnprotectedEndpoint, log, err, "error retrieving user groups", http.StatusInternalServerError, w) + return nil, err } + groups = groupsRes.Groups + _ = userGroupsCache.SetWithExpire(u.Id.OpaqueId, groupsRes.Groups, 3600*time.Second) + } + u.Groups = groups + } + + // ensure access to the resource is allowed + ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path) + if err != nil { + logError(isUnprotectedEndpoint, log, err, "error verifying scope of access token", http.StatusInternalServerError, w) + return nil, err + } + if !ok { + err := errtypes.PermissionDenied("access to resource not allowed") + logError(isUnprotectedEndpoint, log, err, "access to resource not allowed", http.StatusUnauthorized, w) + return nil, err + } - // store user and core access token in context. - ctx = ctxpkg.ContextSetUser(ctx, u) - ctx = ctxpkg.ContextSetToken(ctx, tkn) - ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials? + // store user and core access token in context. + ctx = ctxpkg.ContextSetUser(ctx, u) + ctx = ctxpkg.ContextSetToken(ctx, tkn) + ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials? - ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, r.UserAgent()) + ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, r.UserAgent()) - // store scopes in context - ctx = ctxpkg.ContextSetScopes(ctx, tokenScope) + return ctx, nil +} - r = r.WithContext(ctx) - h.ServeHTTP(w, r) - }) +func logError(isUnprotectedEndpoint bool, log *zerolog.Logger, err error, msg string, status int, w http.ResponseWriter) { + if !isUnprotectedEndpoint { + log.Error().Err(err).Msg(msg) + w.WriteHeader(status) } - return chain, nil } // getCredsForUserAgent returns the WWW Authenticate challenges keys to use given an http request diff --git a/internal/http/services/appprovider/appprovider.go b/internal/http/services/appprovider/appprovider.go index f0527abf64..21ef5b3ee7 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -28,15 +28,15 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/datagateway" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/rhttp" - "github.com/cs3org/reva/v2/pkg/rhttp/global" - "github.com/cs3org/reva/v2/pkg/rhttp/router" - "github.com/cs3org/reva/v2/pkg/sharedconf" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" + "github.com/go-chi/chi/v5" ua "github.com/mileusna/useragent" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -47,7 +47,7 @@ func init() { global.Register("appprovider", New) } -// Config holds the config options that need to be passed down to all ocdav handlers +// Config holds the config options for the HTTP appprovider service type Config struct { Prefix string `mapstructure:"prefix"` GatewaySvc string `mapstructure:"gatewaysvc"` @@ -62,7 +62,8 @@ func (c *Config) init() { } type svc struct { - conf *Config + conf *Config + router *chi.Mux } // New returns a new ocmd object @@ -74,12 +75,26 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) } conf.init() + r := chi.NewRouter() s := &svc{ - conf: conf, + conf: conf, + router: r, } + + if err := s.routerInit(); err != nil { + return nil, err + } + return s, nil } +func (s *svc) routerInit() error { + s.router.Get("/list", s.handleList) + s.router.Post("/new", s.handleNew) + s.router.Post("/open", s.handleOpen) + return nil +} + // Close performs cleanup. func (s *svc) Close() error { return nil @@ -95,29 +110,7 @@ func (s *svc) Unprotected() []string { func (s *svc) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - switch r.Method { - case "POST": - switch head { - case "new": - s.handleNew(w, r) - case "open": - s.handleOpen(w, r) - default: - writeError(w, r, appErrorUnimplemented, "unsupported POST endpoint", nil) - } - case "GET": - switch head { - case "list": - s.handleList(w, r) - default: - writeError(w, r, appErrorUnimplemented, "unsupported GET endpoint", nil) - } - default: - writeError(w, r, appErrorUnimplemented, "unsupported method", nil) - } + s.router.ServeHTTP(w, r) }) } @@ -349,7 +342,6 @@ func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) { fileRef := &provider.Reference{ ResourceId: resourceID, - Path: ".", } statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: fileRef}) diff --git a/internal/http/services/appprovider/errors.go b/internal/http/services/appprovider/errors.go index 139724c608..353925423d 100644 --- a/internal/http/services/appprovider/errors.go +++ b/internal/http/services/appprovider/errors.go @@ -22,7 +22,7 @@ import ( "encoding/json" "net/http" - "github.com/cs3org/reva/v2/pkg/appctx" + "github.com/cs3org/reva/pkg/appctx" ) // appErrorCode stores the type of error encountered diff --git a/internal/http/services/archiver/handler.go b/internal/http/services/archiver/handler.go index c56eae581c..8c9bc366c7 100644 --- a/internal/http/services/archiver/handler.go +++ b/internal/http/services/archiver/handler.go @@ -31,15 +31,15 @@ import ( "regexp" - "github.com/cs3org/reva/v2/internal/http/services/archiver/manager" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/rhttp" - "github.com/cs3org/reva/v2/pkg/rhttp/global" - "github.com/cs3org/reva/v2/pkg/sharedconf" - "github.com/cs3org/reva/v2/pkg/storage/utils/downloader" - "github.com/cs3org/reva/v2/pkg/storage/utils/walker" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/http/services/archiver/manager" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage/utils/downloader" + "github.com/cs3org/reva/pkg/storage/utils/walker" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/gdexlab/go-render/render" ua "github.com/mileusna/useragent" "github.com/mitchellh/mapstructure" @@ -129,8 +129,8 @@ func (s *svc) getResources(ctx context.Context, paths, ids []string) ([]*provide for _, id := range ids { // id is base64 encoded and after decoding has the form : - decodedID := resourceid.OwnCloudResourceIDUnwrap(id) - if decodedID == nil { + ref := resourceid.OwnCloudResourceIDUnwrap(id) + if ref == nil { return nil, errors.New("could not unwrap given file id") } @@ -143,7 +143,7 @@ func (s *svc) getResources(ctx context.Context, paths, ids []string) ([]*provide resp, err := s.gtwClient.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ - Path: p, + ResourceId: ref, }, }) diff --git a/internal/http/services/loader/loader.go b/internal/http/services/loader/loader.go index f15a46bb3c..6de231b478 100644 --- a/internal/http/services/loader/loader.go +++ b/internal/http/services/loader/loader.go @@ -20,21 +20,22 @@ package loader import ( // Load core HTTP services - _ "github.com/cs3org/reva/v2/internal/http/services/appprovider" - _ "github.com/cs3org/reva/v2/internal/http/services/archiver" - _ "github.com/cs3org/reva/v2/internal/http/services/datagateway" - _ "github.com/cs3org/reva/v2/internal/http/services/dataprovider" - _ "github.com/cs3org/reva/v2/internal/http/services/helloworld" - _ "github.com/cs3org/reva/v2/internal/http/services/mentix" - _ "github.com/cs3org/reva/v2/internal/http/services/meshdirectory" - _ "github.com/cs3org/reva/v2/internal/http/services/metrics" - _ "github.com/cs3org/reva/v2/internal/http/services/ocmd" - _ "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav" - _ "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs" - _ "github.com/cs3org/reva/v2/internal/http/services/prometheus" - _ "github.com/cs3org/reva/v2/internal/http/services/reverseproxy" - _ "github.com/cs3org/reva/v2/internal/http/services/siteacc" - _ "github.com/cs3org/reva/v2/internal/http/services/sysinfo" - _ "github.com/cs3org/reva/v2/internal/http/services/wellknown" + _ "github.com/cs3org/reva/internal/http/services/appprovider" + _ "github.com/cs3org/reva/internal/http/services/archiver" + _ "github.com/cs3org/reva/internal/http/services/datagateway" + _ "github.com/cs3org/reva/internal/http/services/dataprovider" + _ "github.com/cs3org/reva/internal/http/services/helloworld" + _ "github.com/cs3org/reva/internal/http/services/mentix" + _ "github.com/cs3org/reva/internal/http/services/meshdirectory" + _ "github.com/cs3org/reva/internal/http/services/metrics" + _ "github.com/cs3org/reva/internal/http/services/ocmd" + _ "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" + _ "github.com/cs3org/reva/internal/http/services/owncloud/ocs" + _ "github.com/cs3org/reva/internal/http/services/preferences" + _ "github.com/cs3org/reva/internal/http/services/prometheus" + _ "github.com/cs3org/reva/internal/http/services/reverseproxy" + _ "github.com/cs3org/reva/internal/http/services/siteacc" + _ "github.com/cs3org/reva/internal/http/services/sysinfo" + _ "github.com/cs3org/reva/internal/http/services/wellknown" // Add your own service here ) diff --git a/internal/http/services/mentix/mentix.go b/internal/http/services/mentix/mentix.go index 09294cbd17..227f61ad44 100644 --- a/internal/http/services/mentix/mentix.go +++ b/internal/http/services/mentix/mentix.go @@ -137,11 +137,6 @@ func applyDefaultConfig(conf *config.Configuration) { conf.Connectors.GOCDB.Scope = "SM" // TODO(Daniel-WWU-IT): This might change in the future } - // Importers - if conf.Importers.SiteRegistration.Endpoint == "" { - conf.Importers.SiteRegistration.Endpoint = "/sitereg" - } - // Exporters addDefaultConnector := func(enabledList *[]string) { if len(*enabledList) == 0 { diff --git a/internal/http/services/ocmd/invites.go b/internal/http/services/ocmd/invites.go index 2a667d7a8b..6f15c1ee6a 100644 --- a/internal/http/services/ocmd/invites.go +++ b/internal/http/services/ocmd/invites.go @@ -26,7 +26,6 @@ import ( "mime" "net/http" "net/url" - "strings" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" @@ -239,10 +238,6 @@ func (h *invitesHandler) acceptInvite(w http.ResponseWriter, r *http.Request) { return } - if !(strings.Contains(recipientProvider, "://")) { - recipientProvider = "https://" + recipientProvider - } - recipientProviderURL, err := url.Parse(recipientProvider) if err != nil { WriteError(w, r, APIErrorServerError, fmt.Sprintf("error parseing recipientProvider URL: %s", recipientProvider), err) diff --git a/internal/http/services/ocmd/send.go b/internal/http/services/ocmd/send.go index a4a84e284e..7391a9c0ec 100644 --- a/internal/http/services/ocmd/send.go +++ b/internal/http/services/ocmd/send.go @@ -33,11 +33,11 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" "google.golang.org/grpc/metadata" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) type sendHandler struct { diff --git a/internal/http/services/ocmd/shares.go b/internal/http/services/ocmd/shares.go index 4a6d5537d6..acdde5148c 100644 --- a/internal/http/services/ocmd/shares.go +++ b/internal/http/services/ocmd/shares.go @@ -141,9 +141,9 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } - shareWithParts := strings.Split(shareWith, "@") + var shareWithParts []string = strings.Split(shareWith, "@") userRes, err := gatewayClient.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{OpaqueId: shareWithParts[0]}, + UserId: &userpb.UserId{OpaqueId: shareWithParts[0]}, SkipFetchingUserGroups: true, }) if err != nil { WriteError(w, r, APIErrorServerError, "error searching recipient", err) diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index 51ea05b438..c41a329702 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -54,21 +54,38 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), "copy") defer span.End() + if s.c.EnableHTTPTpc { + if r.Header.Get("Source") != "" { + // HTTP Third-Party Copy Pull mode + s.handleTPCPull(ctx, w, r, ns) + return + } else if r.Header.Get("Destination") != "" { + // HTTP Third-Party Copy Push mode + s.handleTPCPush(ctx, w, r, ns) + return + } + } + + // Local copy: in this case Destination is mandatory src := path.Join(ns, r.URL.Path) dh := r.Header.Get(net.HeaderDestination) baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) dst, err := net.ParseDestination(baseURI, dh) if err != nil { + appctx.GetLogger(ctx).Warn().Msg("HTTP COPY: failed to extract destination") w.WriteHeader(http.StatusBadRequest) return } + for _, r := range nameRules { if !r.Test(dst) { + appctx.GetLogger(ctx).Warn().Msgf("HTTP COPY: destination %s failed validation", dst) w.WriteHeader(http.StatusBadRequest) return } } + dst = path.Join(ns, dst) sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() diff --git a/internal/http/services/owncloud/ocdav/get.go b/internal/http/services/owncloud/ocdav/get.go index 6b1413ac68..4391ba60d3 100644 --- a/internal/http/services/owncloud/ocdav/get.go +++ b/internal/http/services/owncloud/ocdav/get.go @@ -30,16 +30,13 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/internal/grpc/services/storageprovider" - "github.com/cs3org/reva/v2/internal/http/services/datagateway" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/rhttp" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rhttp" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/rs/zerolog" ) @@ -142,12 +139,12 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ info := sRes.Info - w.Header().Set(net.HeaderContentType, info.MimeType) - w.Header().Set(net.HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ + w.Header().Set(HeaderContentType, info.MimeType) + w.Header().Set(HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ path.Base(r.URL.Path)+"; filename=\""+path.Base(r.URL.Path)+"\"") - w.Header().Set(net.HeaderETag, info.Etag) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(HeaderETag, info.Etag) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(HeaderOCETag, info.Etag) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) w.Header().Set(net.HeaderLastModified, lastModifiedString) diff --git a/internal/http/services/owncloud/ocdav/head.go b/internal/http/services/owncloud/ocdav/head.go index 1882d52a20..312ad1ece5 100644 --- a/internal/http/services/owncloud/ocdav/head.go +++ b/internal/http/services/owncloud/ocdav/head.go @@ -27,8 +27,8 @@ import ( "strings" "time" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils/resourceid" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -91,10 +91,10 @@ func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Req } info := res.Info - w.Header().Set(net.HeaderContentType, info.MimeType) - w.Header().Set(net.HeaderETag, info.Etag) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(HeaderContentType, info.MimeType) + w.Header().Set(HeaderETag, info.Etag) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(HeaderOCETag, info.Etag) if info.Checksum != nil { w.Header().Set(net.HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) } diff --git a/internal/http/services/owncloud/ocdav/meta.go b/internal/http/services/owncloud/ocdav/meta.go index 0acc610ec5..7aaffea142 100644 --- a/internal/http/services/owncloud/ocdav/meta.go +++ b/internal/http/services/owncloud/ocdav/meta.go @@ -21,8 +21,8 @@ package ocdav import ( "net/http" - "github.com/cs3org/reva/v2/pkg/rhttp/router" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/pkg/rhttp/router" + "github.com/cs3org/reva/pkg/utils/resourceid" ) // MetaHandler handles meta requests diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index 0146cd5fa8..2673f7a30c 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -26,14 +26,10 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/rhttp/router" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rhttp/router" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/rs/zerolog" ) @@ -276,9 +272,9 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } info := dstStatRes.Info - w.Header().Set(net.HeaderContentType, info.MimeType) - w.Header().Set(net.HeaderETag, info.Etag) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(HeaderContentType, info.MimeType) + w.Header().Set(HeaderETag, info.Etag) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(HeaderOCETag, info.Etag) w.WriteHeader(successCode) } diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index a21425a383..c3b445eabd 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -20,30 +20,36 @@ package ocdav import ( "context" + "fmt" "net/http" "path" "strings" "time" + "github.com/ReneKroon/ttlcache/v2" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/rhttp" - "github.com/cs3org/reva/v2/pkg/rhttp/global" - "github.com/cs3org/reva/v2/pkg/rhttp/router" - "github.com/cs3org/reva/v2/pkg/sharedconf" - "github.com/cs3org/reva/v2/pkg/storage/favorite" - "github.com/cs3org/reva/v2/pkg/storage/favorite/registry" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/rhttp/router" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage/favorite" + "github.com/cs3org/reva/pkg/storage/favorite/registry" + "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" ) +type ctxKey int + +const ( + ctxKeyBaseURI ctxKey = iota +) + var ( nameRules = [...]nameRule{ nameNotEmpty{}, @@ -85,11 +91,12 @@ type Config struct { // Example: if WebdavNamespace is /users/{{substr 0 1 .Username}}/{{.Username}} // and received path is /docs the internal path will be: // /users///docs - WebdavNamespace string `mapstructure:"webdav_namespace"` - SharesNamespace string `mapstructure:"shares_namespace"` - GatewaySvc string `mapstructure:"gatewaysvc"` - Timeout int64 `mapstructure:"timeout"` - Insecure bool `mapstructure:"insecure"` + WebdavNamespace string `mapstructure:"webdav_namespace"` + GatewaySvc string `mapstructure:"gatewaysvc"` + Timeout int64 `mapstructure:"timeout"` + Insecure bool `mapstructure:"insecure"` + // If true, HTTP COPY will expect the HTTP-TPC (third-party copy) headers + EnableHTTPTpc bool `mapstructure:"enable_http_tpc"` PublicURL string `mapstructure:"public_url"` FavoriteStorageDriver string `mapstructure:"favorite_storage_driver"` FavoriteStorageDrivers map[string]map[string]interface{} `mapstructure:"favorite_storage_drivers"` @@ -105,17 +112,12 @@ func (c *Config) init() { } type svc struct { - c *Config - webDavHandler *WebDavHandler - davHandler *DavHandler - favoritesManager favorite.Manager - client *http.Client - // LockSystem is the lock management system. - LockSystem LockSystem -} - -func (s *svc) Config() *Config { - return s.c + c *Config + webDavHandler *WebDavHandler + davHandler *DavHandler + favoritesManager favorite.Manager + client *http.Client + userIdentifierCache *ttlcache.Cache } func getFavoritesManager(c *Config) (favorite.Manager, error) { @@ -164,9 +166,11 @@ func NewWith(conf *Config, fm favorite.Manager, ls LockSystem, _ *zerolog.Logger rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))), rhttp.Insecure(conf.Insecure), ), - favoritesManager: fm, - LockSystem: ls, + favoritesManager: fm, + userIdentifierCache: ttlcache.NewCache(), } + _ = s.userIdentifierCache.SetTTL(60 * time.Second) + // initialize handlers and set default configs if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil { return nil, err @@ -277,46 +281,6 @@ func (s *svc) ApplyLayout(ctx context.Context, ns string, useLoggedInUserNS bool var requestUsernameOrID string requestUsernameOrID, requestPath = router.ShiftPath(requestPath) - gatewayClient, err := s.getClient() - if err != nil { - return "", "", err - } - - // Check if this is a Userid - userRes, err := gatewayClient.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{OpaqueId: requestUsernameOrID}, - }) - if err != nil { - return "", "", err - } - - // If it's not a userid try if it is a user name - if userRes.Status.Code != rpc.Code_CODE_OK { - res, err := gatewayClient.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: requestUsernameOrID, - }) - if err != nil { - return "", "", err - } - userRes.Status = res.Status - userRes.User = res.User - } - - // If still didn't find a user, fallback - if userRes.Status.Code != rpc.Code_CODE_OK { - userRes.User = &userpb.User{ - Username: requestUsernameOrID, - Id: &userpb.UserId{OpaqueId: requestUsernameOrID}, - } - } - - u = userRes.User - } - - return templates.WithUser(u, ns), requestPath, nil -} - func addAccessHeaders(w http.ResponseWriter, r *http.Request) { headers := w.Header() // the webdav api is accessible from anywhere diff --git a/internal/http/services/owncloud/ocdav/ocdav_test.go b/internal/http/services/owncloud/ocdav/ocdav_test.go index 13f47f4cc3..87fb5204c0 100644 --- a/internal/http/services/owncloud/ocdav/ocdav_test.go +++ b/internal/http/services/owncloud/ocdav/ocdav_test.go @@ -21,9 +21,22 @@ import ( "testing" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/pkg/utils/resourceid" ) +/* +The encodePath method as it is implemented currently is terribly inefficient. +As soon as there are a few special characters which need to be escaped the allocation count rises and the time spent too. +Adding more special characters increases the allocations and the time spent can rise up to a few milliseconds. +Granted this is not a lot on it's own but when a user has tens or hundreds of paths which need to be escaped and contain a few special characters +then this method alone will cost a huge amount of time. +*/ +func BenchmarkEncodePath(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = encodePath("/some/path/Folder %^*(#1)") + } +} + func TestWrapResourceID(t *testing.T) { expected := "storageid" + "!" + "opaqueid" wrapped := resourceid.OwnCloudResourceIDWrap(&providerv1beta1.ResourceId{StorageId: "storageid", OpaqueId: "opaqueid"}) @@ -33,6 +46,63 @@ func TestWrapResourceID(t *testing.T) { } } +func TestExtractDestination(t *testing.T) { + expected := "/dst" + request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) + request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + + ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/dav") + destination, err := extractDestination(request.WithContext(ctx)) + if err != nil { + t.Errorf("Expected err to be nil got %s", err) + } + + if destination != expected { + t.Errorf("Extracted destination is not expected, got %s want %s", destination, expected) + } +} + +func TestExtractDestinationWithoutHeader(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) + + _, err := extractDestination(request) + if err == nil { + t.Errorf("Expected err to be nil got %s", err) + } + + if !errors.Is(err, errInvalidValue) { + t.Errorf("Expected error invalid value, got %s", err) + } +} + +func TestExtractDestinationWithInvalidDestination(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) + request.Header.Set(HeaderDestination, "://example.org/remote.php/dav/dst") + _, err := extractDestination(request) + if err == nil { + t.Errorf("Expected err to be nil got %s", err) + } + + if !errors.Is(err, errInvalidValue) { + t.Errorf("Expected error invalid value, got %s", err) + } +} + +func TestExtractDestinationWithDestinationWrongBaseURI(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) + request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + + ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/webdav") + _, err := extractDestination(request.WithContext(ctx)) + if err == nil { + t.Errorf("Expected err to be nil got %s", err) + } + + if !errors.Is(err, errInvalidValue) { + t.Errorf("Expected error invalid value, got %s", err) + } +} + func TestNameNotEmptyRule(t *testing.T) { tests := map[string]bool{ "": false, diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go new file mode 100644 index 0000000000..7fd60c19cc --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -0,0 +1,1149 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "bytes" + "context" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "net/http" + "net/url" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/publicshare" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" +) + +const ( + _nsDav = "DAV:" + _nsOwncloud = "http://owncloud.org/ns" + _nsOCS = "http://open-collaboration-services.org/ns" + + _propOcFavorite = "http://owncloud.org/ns/favorite" + + // RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781 + RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT" + + // _propQuotaUncalculated = "-1" + _propQuotaUnknown = "-2" + // _propQuotaUnlimited = "-3" +) + +// ns is the namespace that is prefixed to the path in the cs3 namespace +func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns string) { + ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) + defer span.End() + + span.SetAttributes(attribute.String("component", "ocdav")) + + fn := path.Join(ns, r.URL.Path) + + sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + + pf, status, err := readPropfind(r.Body) + if err != nil { + sublog.Debug().Err(err).Msg("error reading propfind request") + w.WriteHeader(status) + return + } + + ref := &provider.Reference{Path: fn} + + parentInfo, resourceInfos, ok := s.getResourceInfos(ctx, w, r, pf, ref, false, sublog) + if !ok { + // getResourceInfos handles responses in case of an error so we can just return here. + return + } + s.propfindResponse(ctx, w, r, ns, pf, parentInfo, resourceInfos, sublog) +} + +func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { + ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "spaces_propfind") + defer span.End() + + sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Logger() + + pf, status, err := readPropfind(r.Body) + if err != nil { + sublog.Debug().Err(err).Msg("error reading propfind request") + w.WriteHeader(status) + return + } + + // retrieve a specific storage space + ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + if err != nil { + sublog.Error().Err(err).Msg("error sending a grpc request") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if rpcStatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcStatus) + return + } + + parentInfo, resourceInfos, ok := s.getResourceInfos(ctx, w, r, pf, ref, true, sublog) + if !ok { + // getResourceInfos handles responses in case of an error so we can just return here. + return + } + + // parentInfo Path is the name but we need / + if r.URL.Path != "" { + parentInfo.Path = r.URL.Path + } else { + parentInfo.Path = "/" + } + + // prefix space id to paths + for i := range resourceInfos { + resourceInfos[i].Path = path.Join("/", spaceID, resourceInfos[i].Path) + } + + s.propfindResponse(ctx, w, r, "", pf, parentInfo, resourceInfos, sublog) + +} + +func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { + ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response") + defer span.End() + + filters := make([]*link.ListPublicSharesRequest_Filter, 0, len(resourceInfos)) + for i := range resourceInfos { + filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id)) + } + + client, err := s.getClient() + if err != nil { + log.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + var linkshares map[string]struct{} + listResp, err := client.ListPublicShares(ctx, &link.ListPublicSharesRequest{Filters: filters}) + if err == nil { + linkshares = make(map[string]struct{}, len(listResp.Share)) + for i := range listResp.Share { + linkshares[listResp.Share[i].ResourceId.OpaqueId] = struct{}{} + } + } else { + log.Error().Err(err).Msg("propfindResponse: couldn't list public shares") + span.SetStatus(codes.Error, err.Error()) + } + + propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, linkshares) + if err != nil { + log.Error().Err(err).Msg("error formatting propfind") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set(HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + + var disableTus bool + // let clients know this collection supports tus.io POST requests to start uploads + if parentInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + if parentInfo.Opaque != nil { + _, disableTus = parentInfo.Opaque.Map["disable_tus"] + } + if !disableTus { + w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ", ")) + w.Header().Set(HeaderTusResumable, "1.0.0") + w.Header().Set(HeaderTusVersion, "1.0.0") + w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + } + } + w.WriteHeader(http.StatusMultiStatus) + if _, err := w.Write([]byte(propRes)); err != nil { + log.Err(err).Msg("error writing response") + } +} + +func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf propfindXML, ref *provider.Reference, spacesPropfind bool, log zerolog.Logger) (*provider.ResourceInfo, []*provider.ResourceInfo, bool) { + depth := r.Header.Get(HeaderDepth) + if depth == "" { + depth = "1" + } + // see https://tools.ietf.org/html/rfc4918#section-9.1 + if depth != "0" && depth != "1" && depth != "infinity" { + log.Debug().Str("depth", depth).Msgf("invalid Depth header value") + w.WriteHeader(http.StatusBadRequest) + m := fmt.Sprintf("Invalid Depth header value: %v", depth) + b, err := Marshal(exception{ + code: SabredavBadRequest, + message: m, + }) + HandleWebdavError(&log, w, b, err) + return nil, nil, false + } + + client, err := s.getClient() + if err != nil { + log.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + + var metadataKeys []string + + if pf.Allprop != nil { + // TODO this changes the behavior and returns all properties if allprops has been set, + // but allprops should only return some default properties + // see https://tools.ietf.org/html/rfc4918#section-9.1 + // the description of arbitrary_metadata_keys in https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.ListContainerRequest an others may need clarification + // tracked in https://github.com/cs3org/cs3apis/issues/104 + metadataKeys = append(metadataKeys, "*") + } else { + for i := range pf.Prop { + if requiresExplicitFetching(&pf.Prop[i]) { + metadataKeys = append(metadataKeys, metadataKeyOf(&pf.Prop[i])) + } + } + } + req := &provider.StatRequest{ + Ref: ref, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.Stat(ctx, req) + if err != nil { + log.Error().Err(err).Interface("req", req).Msg("error sending a grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } else if res.Status.Code != rpc.Code_CODE_OK { + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + w.WriteHeader(http.StatusNotFound) + m := fmt.Sprintf("Resource %v not found", ref.Path) + b, err := Marshal(exception{ + code: SabredavNotFound, + message: m, + }) + HandleWebdavError(&log, w, b, err) + return nil, nil, false + } + HandleErrorStatus(&log, w, res.Status) + return nil, nil, false + } + + if spacesPropfind { + res.Info.Path = ref.Path + } + + parentInfo := res.Info + resourceInfos := []*provider.ResourceInfo{parentInfo} + + switch { + case !spacesPropfind && parentInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER: + // The propfind is requested for a file that exists + // In this case, we can stat the parent directory and return both + parentPath := path.Dir(parentInfo.Path) + resourceInfos = append(resourceInfos, parentInfo) + parentRes, err := client.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{Path: parentPath}, + ArbitraryMetadataKeys: metadataKeys, + }) + if err != nil { + log.Error().Err(err).Interface("req", req).Msg("error sending a grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } else if parentRes.Status.Code != rpc.Code_CODE_OK { + if parentRes.Status.Code == rpc.Code_CODE_NOT_FOUND { + w.WriteHeader(http.StatusNotFound) + m := fmt.Sprintf("Resource %v not found", parentPath) + b, err := Marshal(exception{ + code: SabredavNotFound, + message: m, + }) + HandleWebdavError(&log, w, b, err) + return nil, nil, false + } + HandleErrorStatus(&log, w, parentRes.Status) + return nil, nil, false + } + parentInfo = parentRes.Info + + case parentInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1": + req := &provider.ListContainerRequest{ + Ref: ref, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.ListContainer(ctx, req) + if err != nil { + log.Error().Err(err).Msg("error sending list container grpc request") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + + if res.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&log, w, res.Status) + return nil, nil, false + } + resourceInfos = append(resourceInfos, res.Infos...) + + case depth == "infinity": + // FIXME: doesn't work cross-storage as the results will have the wrong paths! + // use a stack to explore sub-containers breadth-first + stack := []string{parentInfo.Path} + for len(stack) > 0 { + // retrieve path on top of stack + path := stack[len(stack)-1] + + var nRef *provider.Reference + if spacesPropfind { + nRef = &provider.Reference{ + ResourceId: ref.ResourceId, + Path: path, + } + } else { + nRef = &provider.Reference{Path: path} + } + req := &provider.ListContainerRequest{ + Ref: nRef, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.ListContainer(ctx, req) + if err != nil { + log.Error().Err(err).Str("path", path).Msg("error sending list container grpc request") + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + if res.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&log, w, res.Status) + return nil, nil, false + } + + stack = stack[:len(stack)-1] + + // check sub-containers in reverse order and add them to the stack + // the reversed order here will produce a more logical sorting of results + for i := len(res.Infos) - 1; i >= 0; i-- { + if spacesPropfind { + res.Infos[i].Path = utils.MakeRelativePath(filepath.Join(nRef.Path, res.Infos[i].Path)) + } + if res.Infos[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, res.Infos[i].Path) + } + } + + resourceInfos = append(resourceInfos, res.Infos...) + + if depth != "infinity" { + break + } + + // TODO: stream response to avoid storing too many results in memory + } + } + + return parentInfo, resourceInfos, true +} + +func requiresExplicitFetching(n *xml.Name) bool { + switch n.Space { + case _nsDav: + switch n.Local { + case "quota-available-bytes", "quota-used-bytes": + // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes + // from https://www.rfc-editor.org/rfc/rfc4331.html#section-2 + return true + default: + return false + } + case _nsOwncloud: + switch n.Local { + case "favorite", "share-types", "checksums", "size": + return true + default: + return false + } + case _nsOCS: + return false + } + return true +} + +// from https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/xml.go#L178-L205 +func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { + c := countingReader{r: r} + if err = xml.NewDecoder(&c).Decode(&pf); err != nil { + if err == io.EOF { + if c.n == 0 { + // An empty body means to propfind allprop. + // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND + return propfindXML{Allprop: new(struct{})}, 0, nil + } + err = errInvalidPropfind + } + return propfindXML{}, http.StatusBadRequest, err + } + + if pf.Allprop == nil && pf.Include != nil { + return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + } + if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { + return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + } + if pf.Prop != nil && pf.Propname != nil { + return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + } + if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { + // jfd: I think is perfectly valid ... treat it as allprop + return propfindXML{Allprop: new(struct{})}, 0, nil + } + return pf, 0, nil +} + +func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, linkshares map[string]struct{}) (string, error) { + responses := make([]*responseXML, 0, len(mds)) + for i := range mds { + res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, linkshares) + if err != nil { + return "", err + } + responses = append(responses, res) + } + responsesXML, err := xml.Marshal(&responses) + if err != nil { + return "", err + } + + msg := `` + msg += string(responsesXML) + `` + return msg, nil +} + +func (s *svc) xmlEscaped(val string) []byte { + buf := new(bytes.Buffer) + xml.Escape(buf, []byte(val)) + return buf.Bytes() +} + +func (s *svc) newPropNS(namespace string, local string, val string) *propertyXML { + return &propertyXML{ + XMLName: xml.Name{Space: namespace, Local: local}, + Lang: "", + InnerXML: s.xmlEscaped(val), + } +} + +// TODO properly use the space +func (s *svc) newProp(key, val string) *propertyXML { + return &propertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: s.xmlEscaped(val), + } +} + +// TODO properly use the space +func (s *svc) newPropRaw(key, val string) *propertyXML { + return &propertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: []byte(val), + } +} + +// mdToPropResponse converts the CS3 metadata into a webdav PropResponse +// ns is the CS3 namespace that needs to be removed from the CS3 path before +// prefixing it with the baseURI +func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, linkshares map[string]struct{}) (*responseXML, error) { + sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger() + md.Path = strings.TrimPrefix(md.Path, ns) + + baseURI := ctx.Value(ctxKeyBaseURI).(string) + + ref := path.Join(baseURI, md.Path) + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + ref += "/" + } + + response := responseXML{ + Href: encodePath(ref), + Propstat: []propstatXML{}, + } + + var ls *link.PublicShare + + // -1 indicates uncalculated + // -2 indicates unknown (default) + // -3 indicates unlimited + quota := _propQuotaUnknown + size := fmt.Sprintf("%d", md.Size) + // TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err + // or use ok like pattern and return bool? + if md.Opaque != nil && md.Opaque.Map != nil { + if md.Opaque.Map["link-share"] != nil && md.Opaque.Map["link-share"].Decoder == "json" { + ls = &link.PublicShare{} + err := json.Unmarshal(md.Opaque.Map["link-share"].Value, ls) + if err != nil { + sublog.Error().Err(err).Msg("could not unmarshal link json") + } + } + if md.Opaque.Map["quota"] != nil && md.Opaque.Map["quota"].Decoder == "plain" { + quota = string(md.Opaque.Map["quota"].Value) + } + } + + role := conversions.RoleFromResourcePermissions(md.PermissionSet) + + isShared := !isCurrentUserOwner(ctx, md.Owner) + var wdp string + isPublic := ls != nil + if md.PermissionSet != nil { + wdp = role.WebDAVPermissions( + md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, + isShared, + false, + isPublic, + ) + sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") + } + + propstatOK := propstatXML{ + Status: "HTTP/1.1 200 OK", + Prop: []*propertyXML{}, + } + propstatNotFound := propstatXML{ + Status: "HTTP/1.1 404 Not Found", + Prop: []*propertyXML{}, + } + // when allprops has been requested + if pf.Allprop != nil { + // return all known properties + + if md.Id != nil { + id := resourceid.OwnCloudResourceIDWrap(md.Id) + propstatOK.Prop = append(propstatOK.Prop, + s.newProp("oc:id", id), + s.newProp("oc:fileid", id), + ) + } + + if md.Etag != "" { + // etags must be enclosed in double quotes and cannot contain them. + // See https://tools.ietf.org/html/rfc7232#section-2.3 for details + // TODO(jfd) handle weak tags that start with 'W/' + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + } + + if md.PermissionSet != nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + } + + // always return size, well nearly always ... public link shares are a little weird + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + if ls == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + } + // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes + // from https://www.rfc-editor.org/rfc/rfc4331.html#section-2 + // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) + // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + } else { + propstatOK.Prop = append(propstatOK.Prop, + s.newProp("d:resourcetype", ""), + s.newProp("d:getcontentlength", size), + ) + if md.MimeType != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + } + } + // Finder needs the getLastModified property to work. + if md.Mtime != nil { + t := utils.TSToTime(md.Mtime).UTC() + lastModifiedString := t.Format(RFC1123) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + } + + // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 + var checksums strings.Builder + if md.Checksum != nil { + checksums.WriteString("") + checksums.WriteString(strings.ToUpper(string(storageprovider.GRPC2PKGXS(md.Checksum.Type)))) + checksums.WriteString(":") + checksums.WriteString(md.Checksum.Sum) + } + if md.Opaque != nil { + if e, ok := md.Opaque.Map["md5"]; ok { + if checksums.Len() == 0 { + checksums.WriteString("MD5:") + } else { + checksums.WriteString(" MD5:") + } + checksums.WriteString(string(e.Value)) + } + if e, ok := md.Opaque.Map["adler32"]; ok { + if checksums.Len() == 0 { + checksums.WriteString("ADLER32:") + } else { + checksums.WriteString(" ADLER32:") + } + checksums.WriteString(string(e.Value)) + } + } + if checksums.Len() > 0 { + checksums.WriteString("") + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + } + + // ls do not report any properties as missing by default + if ls == nil { + // favorites from arbitrary metadata + if k := md.GetArbitraryMetadata(); k == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } else if amd := k.GetMetadata(); amd == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } else if v, ok := amd[_propOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v)) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } + } + // TODO return other properties ... but how do we put them in a namespace? + } else { + // otherwise return only the requested properties + var ownerUsername, ownerDisplayName string + owner, err := s.getOwnerInfo(ctx, md.Owner) + if err == nil { + ownerUsername = owner.Username + ownerDisplayName = owner.DisplayName + } + + for i := range pf.Prop { + switch pf.Prop[i].Space { + case _nsOwncloud: + switch pf.Prop[i].Local { + // TODO(jfd): maybe phoenix and the other clients can just use this id as an opaque string? + // I tested the desktop client and phoenix to annotate which properties are requestted, see below cases + case "fileid": // phoenix only + if md.Id != nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:fileid", "")) + } + case "id": // desktop client only + if md.Id != nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:id", "")) + } + case "permissions": // both + // oc:permissions take several char flags to indicate the permissions the user has on this node: + // D = delete + // NV = update (renameable moveable) + // W = update (files only) + // CK = create (folders only) + // S = Shared + // R = Shareable (Reshare) + // M = Mounted + // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + case "public-link-permission": // only on a share root node + if ls != nil && md.PermissionSet != nil { + propstatOK.Prop = append( + propstatOK.Prop, + s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-permission", "")) + } + case "public-link-item-type": // only on a share root node + if ls != nil { + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "folder")) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "file")) + // redirectref is another option + } + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-item-type", "")) + } + case "public-link-share-datetime": + if ls != nil && ls.Mtime != nil { + t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime? + shareTimeString := t.Format(RFC1123) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-datetime", shareTimeString)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-datetime", "")) + } + case "public-link-share-owner": + if ls != nil && ls.Owner != nil { + if isCurrentUserOwner(ctx, ls.Owner) { + u := ctxpkg.ContextMustGetUser(ctx) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-owner", u.Username)) + } else { + u, _ := ctxpkg.ContextGetUser(ctx) + sublog.Error().Interface("share", ls).Interface("user", u).Msg("the current user in the context should be the owner of a public link share") + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + } + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + } + case "public-link-expiration": + if ls != nil && ls.Expiration != nil { + t := utils.TSToTime(ls.Expiration).UTC() + expireTimeString := t.Format(RFC1123) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-expiration", expireTimeString)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + } + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + case "size": // phoenix only + // TODO we cannot find out if md.Size is set or not because ints in go default to 0 + // TODO what is the difference to d:quota-used-bytes (which only exists for collections)? + // oc:size is available on files and folders and behaves like d:getcontentlength or d:quota-used-bytes respectively + if ls == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + } else { + // link share root collection has no size + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", "")) + } + case "owner-id": // phoenix only + if ownerUsername != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", ownerUsername)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + } + case "favorite": // phoenix only + // TODO: can be 0 or 1?, in oc10 it is present or not + // TODO: read favorite via separate call? that would be expensive? I hope it is in the md + // TODO: this boolean favorite property is so horribly wrong ... either it is presont, or it is not ... unless ... it is possible to have a non binary value ... we need to double check + if ls == nil { + if k := md.GetArbitraryMetadata(); k == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } else if amd := k.GetMetadata(); amd == nil { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } else if v, ok := amd[_propOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1")) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + } + } else { + // link share root collection has no favorite + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:favorite", "")) + } + case "checksums": // desktop ... not really ... the desktop sends the OC-Checksum header + + // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 + var checksums strings.Builder + if md.Checksum != nil { + checksums.WriteString("") + checksums.WriteString(strings.ToUpper(string(storageprovider.GRPC2PKGXS(md.Checksum.Type)))) + checksums.WriteString(":") + checksums.WriteString(md.Checksum.Sum) + } + if md.Opaque != nil { + if e, ok := md.Opaque.Map["md5"]; ok { + if checksums.Len() == 0 { + checksums.WriteString("MD5:") + } else { + checksums.WriteString(" MD5:") + } + checksums.WriteString(string(e.Value)) + } + if e, ok := md.Opaque.Map["adler32"]; ok { + if checksums.Len() == 0 { + checksums.WriteString("ADLER32:") + } else { + checksums.WriteString(" ADLER32:") + } + checksums.WriteString(string(e.Value)) + } + } + if checksums.Len() > 13 { + checksums.WriteString("") + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:checksums", "")) + } + case "share-types": // desktop + var types strings.Builder + k := md.GetArbitraryMetadata() + amd := k.GetMetadata() + if amdv, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok { + types.WriteString("") + types.WriteString(amdv) + types.WriteString("") + } + + if md.Id != nil { + if _, ok := linkshares[md.Id.OpaqueId]; ok { + types.WriteString("3") + } + } + + if types.Len() != 0 { + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:share-types", types.String())) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + } + case "owner-display-name": // phoenix only + if ownerDisplayName != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", ownerDisplayName)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + } + case "downloadURL": // desktop + if isPublic && md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + var path string + if !ls.PasswordProtected { + path = md.Path + } else { + expiration := time.Unix(int64(ls.Signature.SignatureExpiration.Seconds), int64(ls.Signature.SignatureExpiration.Nanos)) + var sb strings.Builder + + sb.WriteString(md.Path) + sb.WriteString("?signature=") + sb.WriteString(ls.Signature.Signature) + sb.WriteString("&expiration=") + sb.WriteString(url.QueryEscape(expiration.Format(time.RFC3339))) + + path = sb.String() + } + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:downloadURL", s.c.PublicURL+baseURI+path)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + } + case "signature-auth": + if isPublic { + // We only want to add the attribute to the root of the propfind. + if strings.HasSuffix(md.Path, ls.Token) && ls.Signature != nil { + expiration := time.Unix(int64(ls.Signature.SignatureExpiration.Seconds), int64(ls.Signature.SignatureExpiration.Nanos)) + var sb strings.Builder + sb.WriteString("") + sb.WriteString(ls.Signature.Signature) + sb.WriteString("") + sb.WriteString("") + sb.WriteString(expiration.Format(time.RFC3339)) + sb.WriteString("") + + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:signature-auth", sb.String())) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:signature-auth", "")) + } + } + case "privatelink": // phoenix only + // https://phoenix.owncloud.com/f/9 + fallthrough + case "dDC": // desktop + fallthrough + case "data-fingerprint": // desktop + // used by admins to indicate a backup has been restored, + // can only occur on the root node + // server implementation in https://github.com/owncloud/core/pull/24054 + // see https://doc.owncloud.com/server/admin_manual/configuration/server/occ_command.html#maintenance-commands + // TODO(jfd): double check the client behavior with reva on backup restore + fallthrough + default: + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + } + case _nsDav: + switch pf.Prop[i].Local { + case "getetag": // both + if md.Etag != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getetag", "")) + } + case "getcontentlength": // both + // see everts stance on this https://stackoverflow.com/a/31621912, he points to http://tools.ietf.org/html/rfc4918#section-15.3 + // > Purpose: Contains the Content-Length header returned by a GET without accept headers. + // which only would make sense when eg. rendering a plain HTML filelisting when GETing a collection, + // which is not the case ... so we don't return it on collections. owncloud has oc:size for that + // TODO we cannot find out if md.Size is set or not because ints in go default to 0 + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontentlength", "")) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + } + case "resourcetype": // both + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + } else { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + // redirectref is another option + } + case "getcontenttype": // phoenix + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + // directories have no contenttype + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontenttype", "")) + } else if md.MimeType != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + } + case "getlastmodified": // both + // TODO we cannot find out if md.Mtime is set or not because ints in go default to 0 + if md.Mtime != nil { + t := utils.TSToTime(md.Mtime).UTC() + lastModifiedString := t.Format(RFC1123) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getlastmodified", "")) + } + case "quota-used-bytes": // RFC 4331 + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + // always returns the current usage, + // in oc10 there seems to be a bug that makes the size in webdav differ from the one in the user properties, not taking shares into account + // in ocis we plan to always mak the quota a property of the storage space + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-used-bytes", "")) + } + case "quota-available-bytes": // RFC 4331 + if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + // oc10 returns -3 for unlimited, -2 for unknown, -1 for uncalculated + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-available-bytes", "")) + } + default: + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + } + case _nsOCS: + switch pf.Prop[i].Local { + // ocs:share-permissions indicate clients the maximum permissions that can be granted: + // 1 = read + // 2 = write (update) + // 4 = create + // 8 = delete + // 16 = share + // shared files can never have the create or delete permission bit set + case "share-permissions": + if md.PermissionSet != nil { + perms := role.OCSPermissions() + // shared files cant have the create or delete permission set + if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + perms &^= conversions.PermissionCreate + perms &^= conversions.PermissionDelete + } + propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) + } + default: + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + } + default: + // handle custom properties + if k := md.GetArbitraryMetadata(); k == nil { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + } else if amd := k.GetMetadata(); amd == nil { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + } else if v, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) + } else { + propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + } + } + } + } + + if len(propstatOK.Prop) > 0 { + response.Propstat = append(response.Propstat, propstatOK) + } + if len(propstatNotFound.Prop) > 0 { + response.Propstat = append(response.Propstat, propstatNotFound) + } + + return &response, nil +} + +// be defensive about wrong encoded etags +func quoteEtag(etag string) string { + if strings.HasPrefix(etag, "W/") { + return `W/"` + strings.Trim(etag[2:], `"`) + `"` + } + return `"` + strings.Trim(etag, `"`) + `"` +} + +func (s *svc) getOwnerInfo(ctx context.Context, owner *userv1beta1.UserId) (*userv1beta1.User, error) { + if owner == nil { + return nil, errtypes.NotFound("owner is nil") + } + + if isCurrentUserOwner(ctx, owner) { + return ctxpkg.ContextMustGetUser(ctx), nil + } + + log := appctx.GetLogger(ctx) + if idIf, err := s.userIdentifierCache.Get(owner.OpaqueId); err == nil { + log.Debug().Msg("cache hit") + return idIf.(*userv1beta1.User), nil + } + + client, err := s.getClient() + if err != nil { + log.Error().Err(err).Msg("error getting grpc client") + return nil, err + } + + res, err := client.GetUser(ctx, &userv1beta1.GetUserRequest{UserId: owner}) + if err != nil { + log.Err(err).Msg("could not look up user") + return nil, err + } + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + log.Err(err).Msg("get user call failed") + return nil, err + } + if res.User == nil { + log.Debug().Msg("user not found") + return nil, err + } + _ = s.userIdentifierCache.Set(owner.OpaqueId, res.User) + return res.User, nil +} + +// a file is only yours if you are the owner +func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { + contextUser, ok := ctxpkg.ContextGetUser(ctx) + if ok && contextUser.Id != nil && owner != nil && + contextUser.Id.Idp == owner.Idp && + contextUser.Id.OpaqueId == owner.OpaqueId { + return true + } + return false +} + +type countingReader struct { + n int + r io.Reader +} + +func (c *countingReader) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + c.n += n + return n, err +} + +func metadataKeyOf(n *xml.Name) string { + switch { + case n.Space == _nsDav && n.Local == "quota-available-bytes": + return "quota" + default: + return fmt.Sprintf("%s/%s", n.Space, n.Local) + } +} + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) +type propfindProps []xml.Name + +// UnmarshalXML appends the property names enclosed within start to pn. +// +// It returns an error if start does not contain any properties or if +// properties contain values. Character data between properties is ignored. +func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + t, err := next(d) + if err != nil { + return err + } + switch e := t.(type) { + case xml.EndElement: + // jfd: I think is perfectly valid ... treat it as allprop + /* + if len(*pn) == 0 { + return fmt.Errorf("%s must not be empty", start.Name.Local) + } + */ + return nil + case xml.StartElement: + t, err = next(d) + if err != nil { + return err + } + if _, ok := t.(xml.EndElement); !ok { + return fmt.Errorf("unexpected token %T", t) + } + *pn = append(*pn, e.Name) + } + } +} + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind +type propfindXML struct { + XMLName xml.Name `xml:"DAV: propfind"` + Allprop *struct{} `xml:"DAV: allprop"` + Propname *struct{} `xml:"DAV: propname"` + Prop propfindProps `xml:"DAV: prop"` + Include propfindProps `xml:"DAV: include"` +} + +type responseXML struct { + XMLName xml.Name `xml:"d:response"` + Href string `xml:"d:href"` + Propstat []propstatXML `xml:"d:propstat"` + Status string `xml:"d:status,omitempty"` + Error *errorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` +} + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat +type propstatXML struct { + // Prop requires DAV: to be the default namespace in the enclosing + // XML. This is due to the standard encoding/xml package currently + // not honoring namespace declarations inside a xmltag with a + // parent element for anonymous slice elements. + // Use of multistatusWriter takes care of this. + Prop []*propertyXML `xml:"d:prop>_ignored_"` + Status string `xml:"d:status"` + Error *errorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` +} + +// Property represents a single DAV resource property as defined in RFC 4918. +// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties +type propertyXML struct { + // XMLName is the fully qualified name that identifies this property. + XMLName xml.Name + + // Lang is an optional xml:lang attribute. + Lang string `xml:"xml:lang,attr,omitempty"` + + // InnerXML contains the XML representation of the property value. + // See http://www.webdav.org/specs/rfc4918.html#property_values + // + // Property values of complex type or mixed-content must have fully + // expanded XML namespaces or be self-contained with according + // XML namespace declarations. They must not rely on any XML + // namespace declarations within the scope of the XML document, + // even including the DAV: namespace. + InnerXML []byte `xml:",innerxml"` +} diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 00d584be54..8e4418c5b9 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -83,6 +83,44 @@ func (h *PublicFileHandler) Handler(s *svc) http.Handler { }) } +func (s *svc) adjustResourcePathInURL(w http.ResponseWriter, r *http.Request) bool { + ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "adjustResourcePathInURL") + defer span.End() + + // find actual file name + tokenStatInfo := ctx.Value(tokenStatInfoKey{}).(*provider.ResourceInfo) + sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() + + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return false + } + pathRes, err := client.GetPath(ctx, &provider.GetPathRequest{ + ResourceId: tokenStatInfo.GetId(), + }) + if err != nil { + sublog.Error().Msg("Could not get path of resource") + w.WriteHeader(http.StatusInternalServerError) + return false + } + if pathRes.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, pathRes.Status) + return false + } + if path.Base(r.URL.Path) != path.Base(pathRes.Path) { + sublog.Debug(). + Str("requestbase", path.Base(r.URL.Path)). + Str("pathbase", path.Base(pathRes.Path)). + Msg("base paths don't match") + w.WriteHeader(http.StatusConflict) + return false + } + + return true +} + // ns is the namespace that is prefixed to the path in the cs3 namespace func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns string, onContainer bool) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "token_propfind") @@ -107,10 +145,33 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s return } - // prefix tokenStatInfo.Path with token - tokenStatInfo.Path = filepath.Join(r.URL.Path, tokenStatInfo.Path) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - infos := s.getPublicFileInfos(onContainer, depth == net.DepthZero, tokenStatInfo) + // find actual file name + pathRes, err := client.GetPath(ctx, &provider.GetPathRequest{ + ResourceId: tokenStatInfo.GetId(), + }) + if err != nil { + sublog.Warn().Msg("Could not get path of resource") + w.WriteHeader(http.StatusInternalServerError) + return + } + if pathRes.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, pathRes.Status) + return + } + + if !onContainer && path.Base(r.URL.Path) != path.Base(pathRes.Path) { + // if queried on the wrong path, return not found + w.WriteHeader(http.StatusNotFound) + return + } + infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, "", nil) if err != nil { diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index 2bd34dbd67..1fd76456b1 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -29,17 +29,14 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/datagateway" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rhttp" - "github.com/cs3org/reva/v2/pkg/storage/utils/chunking" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/storage/utils/chunking" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/rs/zerolog" ) @@ -325,10 +322,10 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ newInfo := sRes.Info - w.Header().Add(net.HeaderContentType, newInfo.MimeType) - w.Header().Set(net.HeaderETag, newInfo.Etag) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) - w.Header().Set(net.HeaderOCETag, newInfo.Etag) + w.Header().Add(HeaderContentType, newInfo.MimeType) + w.Header().Set(HeaderETag, newInfo.Etag) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) + w.Header().Set(HeaderOCETag, newInfo.Etag) t := utils.TSToTime(newInfo.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) w.Header().Set(net.HeaderLastModified, lastModifiedString) diff --git a/internal/http/services/owncloud/ocdav/tpc.go b/internal/http/services/owncloud/ocdav/tpc.go new file mode 100644 index 0000000000..c31b6066a4 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/tpc.go @@ -0,0 +1,415 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "context" + "fmt" + "io" + "net/http" + "path" + "strconv" + "strings" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rhttp" +) + +const ( + // PerfMarkerResponseTime corresponds to the interval at which a performance marker is sent back to the TPC client + PerfMarkerResponseTime float64 = 5 +) + +// PerfResponse provides a single chunk of permormance marker response +type PerfResponse struct { + Timestamp time.Time + Bytes uint64 + Index int + Count int +} + +func (p *PerfResponse) getPerfResponseString() string { + var sb strings.Builder + sb.WriteString("Perf Marker\n") + sb.WriteString("Timestamp: " + strconv.FormatInt(p.Timestamp.Unix(), 10) + "\n") + sb.WriteString("Stripe Bytes Transferred: " + strconv.FormatUint(p.Bytes, 10) + "\n") + sb.WriteString("Strip Index: " + strconv.Itoa(p.Index) + "\n") + sb.WriteString("Total Stripe Count: " + strconv.Itoa(p.Count) + "\n") + sb.WriteString("End\n") + return sb.String() +} + +// WriteCounter counts the number of bytes transferred and reports +// back to the TPC client about the progress of the transfer +// through the performance marker response stream. +type WriteCounter struct { + Total uint64 + PrevTime time.Time + w http.ResponseWriter +} + +// SendPerfMarker flushes a single chunk (performance marker) as +// part of the chunked transfer encoding scheme. +func (wc *WriteCounter) SendPerfMarker(size uint64) { + flusher, ok := wc.w.(http.Flusher) + if !ok { + panic("expected http.ResponseWriter to be an http.Flusher") + } + perfResp := PerfResponse{time.Now(), size, 0, 1} + pString := perfResp.getPerfResponseString() + fmt.Fprintln(wc.w, pString) + flusher.Flush() +} + +func (wc *WriteCounter) Write(p []byte) (int, error) { + + n := len(p) + wc.Total += uint64(n) + NowTime := time.Now() + + diff := NowTime.Sub(wc.PrevTime).Seconds() + if diff >= PerfMarkerResponseTime { + wc.SendPerfMarker(wc.Total) + wc.PrevTime = NowTime + } + return n, nil +} + +// +// An example of an HTTP TPC Pull +// +// +-----------------+ GET +----------------+ +// | Src server | <---------------- | Dest server | +// | (Remote) | ----------------> | (Reva) | +// +-----------------+ Data +----------------+ +// ^ +// | +// | COPY +// | +// +----------+ +// | Client | +// +----------+ + +// handleTPCPull performs a GET request on the remote site and upload it +// the requested reva endpoint. +func (s *svc) handleTPCPull(ctx context.Context, w http.ResponseWriter, r *http.Request, ns string) { + src := r.Header.Get("Source") + dst := path.Join(ns, r.URL.Path) + sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() + + overwrite, err := extractOverwrite(w, r) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) + sublog.Warn().Msgf("HTTP TPC Pull: %s", m) + b, err := Marshal(exception{ + code: SabredavBadRequest, + message: m, + }) + HandleWebdavError(&sublog, w, b, err) + return + } + sublog.Debug().Str("overwrite", overwrite).Msg("TPC Pull") + + // get Gateway client + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + // check if destination exists + ref := &provider.Reference{Path: dst} + dstStatReq := &provider.StatRequest{Ref: ref} + dstStatRes, err := client.Stat(ctx, dstStatReq) + if err != nil { + sublog.Error().Err(err).Msg("error sending grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { + HandleErrorStatus(&sublog, w, dstStatRes.Status) + return + } + if dstStatRes.Status.Code == rpc.Code_CODE_OK && overwrite == "F" { + sublog.Warn().Str("overwrite", overwrite).Msg("Destination already exists") + w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 + return + } + + err = s.performHTTPPull(ctx, client, r, w, ns) + if err != nil { + sublog.Error().Err(err).Msg("error performing TPC Pull") + return + } + fmt.Fprintf(w, "success: Created") +} + +func (s *svc) performHTTPPull(ctx context.Context, client gateway.GatewayAPIClient, r *http.Request, w http.ResponseWriter, ns string) error { + src := r.Header.Get("Source") + dst := path.Join(ns, r.URL.Path) + sublog := appctx.GetLogger(ctx) + sublog.Debug().Str("src", src).Str("dst", dst).Msg("Performing HTTP Pull") + + // get http client for remote + httpClient := &http.Client{} + + req, err := http.NewRequest("GET", src, nil) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + + // add authentication header + bearerHeader := r.Header.Get(HeaderTransferAuth) + req.Header.Add("Authorization", bearerHeader) + + // do download + httpDownloadRes, err := httpClient.Do(req) // lgtm[go/request-forgery] + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + defer httpDownloadRes.Body.Close() + + if httpDownloadRes.StatusCode == http.StatusNotImplemented { + w.WriteHeader(http.StatusBadRequest) + return errtypes.NotSupported("Third-Party copy not supported, source might be a folder") + } + if httpDownloadRes.StatusCode != http.StatusOK { + w.WriteHeader(httpDownloadRes.StatusCode) + return errtypes.InternalError(fmt.Sprintf("Remote GET returned status code %d", httpDownloadRes.StatusCode)) + } + + // get upload url + uReq := &provider.InitiateFileUploadRequest{ + Ref: &provider.Reference{Path: dst}, + Opaque: &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "sizedeferred": { + Value: []byte("true"), + }, + }, + }, + } + uRes, err := client.InitiateFileUpload(ctx, uReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + + if uRes.Status.Code != rpc.Code_CODE_OK { + w.WriteHeader(http.StatusInternalServerError) + return fmt.Errorf("status code %d", uRes.Status.Code) + } + + var uploadEP, uploadToken string + for _, p := range uRes.Protocols { + if p.Protocol == "simple" { + uploadEP, uploadToken = p.UploadEndpoint, p.Token + } + } + + // send performance markers periodically every PerfMarkerResponseTime (5 seconds unless configured) + w.WriteHeader(http.StatusAccepted) + wc := WriteCounter{0, time.Now(), w} + tempReader := io.TeeReader(httpDownloadRes.Body, &wc) + + // do Upload + httpUploadReq, err := rhttp.NewRequest(ctx, "PUT", uploadEP, tempReader) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken) + httpUploadRes, err := s.client.Do(httpUploadReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + + defer httpUploadRes.Body.Close() + if httpUploadRes.StatusCode != http.StatusOK { + w.WriteHeader(httpUploadRes.StatusCode) + return err + } + return nil +} + +// +// An example of an HTTP TPC Push +// +// +-----------------+ PUT +----------------+ +// | Dest server | <---------------- | Src server | +// | (Remote) | ----------------> | (Reva) | +// +-----------------+ Done +----------------+ +// ^ +// | +// | COPY +// | +// +----------+ +// | Client | +// +----------+ + +// handleTPCPush performs a PUT request on the remote site and while downloading +// data from the requested reva endpoint. +func (s *svc) handleTPCPush(ctx context.Context, w http.ResponseWriter, r *http.Request, ns string) { + src := path.Join(ns, r.URL.Path) + dst := r.Header.Get("Destination") + sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() + + overwrite, err := extractOverwrite(w, r) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) + sublog.Warn().Msgf("HTTP TPC Push: %s", m) + b, err := Marshal(exception{ + code: SabredavBadRequest, + message: m, + }) + HandleWebdavError(&sublog, w, b, err) + return + } + + sublog.Debug().Str("overwrite", overwrite).Msg("TPC Push") + + // get Gateway client + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + ref := &provider.Reference{Path: src} + srcStatReq := &provider.StatRequest{Ref: ref} + srcStatRes, err := client.Stat(ctx, srcStatReq) + if err != nil { + sublog.Error().Err(err).Msg("error sending grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if srcStatRes.Status.Code != rpc.Code_CODE_OK && srcStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { + HandleErrorStatus(&sublog, w, srcStatRes.Status) + return + } + if srcStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + sublog.Error().Msg("Third-Party copy of a folder is not supported") + w.WriteHeader(http.StatusBadRequest) + return + } + + err = s.performHTTPPush(ctx, client, r, w, srcStatRes.Info, ns) + if err != nil { + sublog.Error().Err(err).Msg("error performing TPC Push") + return + } + fmt.Fprintf(w, "success: Created") +} + +func (s *svc) performHTTPPush(ctx context.Context, client gateway.GatewayAPIClient, r *http.Request, w http.ResponseWriter, srcInfo *provider.ResourceInfo, ns string) error { + src := path.Join(ns, r.URL.Path) + dst := r.Header.Get("Destination") + + sublog := appctx.GetLogger(ctx) + sublog.Debug().Str("src", src).Str("dst", dst).Msg("Performing HTTP Push") + + // get download url + dReq := &provider.InitiateFileDownloadRequest{ + Ref: &provider.Reference{Path: src}, + } + + dRes, err := client.InitiateFileDownload(ctx, dReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + + if dRes.Status.Code != rpc.Code_CODE_OK { + w.WriteHeader(http.StatusInternalServerError) + return fmt.Errorf("status code %d", dRes.Status.Code) + } + + var downloadEP, downloadToken string + for _, p := range dRes.Protocols { + if p.Protocol == "simple" { + downloadEP, downloadToken = p.DownloadEndpoint, p.Token + } + } + + // do download + httpDownloadReq, err := rhttp.NewRequest(ctx, "GET", downloadEP, nil) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + httpDownloadReq.Header.Set(datagateway.TokenTransportHeader, downloadToken) + + httpDownloadRes, err := s.client.Do(httpDownloadReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + defer httpDownloadRes.Body.Close() + if httpDownloadRes.StatusCode != http.StatusOK { + w.WriteHeader(httpDownloadRes.StatusCode) + return fmt.Errorf("Remote PUT returned status code %d", httpDownloadRes.StatusCode) + } + + // send performance markers periodically every PerfMarkerResponseTime (5 seconds unless configured) + w.WriteHeader(http.StatusAccepted) + wc := WriteCounter{0, time.Now(), w} + tempReader := io.TeeReader(httpDownloadRes.Body, &wc) + + // get http client for a remote call + httpClient := &http.Client{} + req, err := http.NewRequest("PUT", dst, tempReader) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + + // add authentication header and content length + bearerHeader := r.Header.Get(HeaderTransferAuth) + req.Header.Add("Authorization", bearerHeader) + req.ContentLength = int64(srcInfo.GetSize()) + + // do Upload + httpUploadRes, err := httpClient.Do(req) // lgtm[go/request-forgery] + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + defer httpUploadRes.Body.Close() + + if httpUploadRes.StatusCode != http.StatusOK { + w.WriteHeader(httpUploadRes.StatusCode) + return err + } + + return nil +} diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 158acfe6ec..1385d10f64 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -29,13 +29,8 @@ import ( "strings" "time" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils/resourceid" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -190,8 +185,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - if depth == net.DepthZero { - propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.StorageId, refBase, nil, nil) + if depth == "0" { + propRes, err := h.formatTrashPropfind(ctx, s, u, nil, nil, basePath) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -275,7 +270,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } } - propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.StorageId, refBase, &pf, items) + propRes, err := h.formatTrashPropfind(ctx, s, u, &pf, items, basePath) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -291,8 +286,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } } -func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, spaceID, refBase string, pf *propfind.XML, items []*provider.RecycleItem) ([]byte, error) { - responses := make([]*propfind.ResponseXML, 0, len(items)+1) +func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, items []*provider.RecycleItem, basePath string) (string, error) { + responses := make([]*responseXML, 0, len(items)+1) // add trashbin dir . entry responses = append(responses, &propfind.ResponseXML{ Href: net.EncodePath(ctx.Value(net.CtxKeyBaseURI).(string) + "/"), // url encode response.Href TODO @@ -316,7 +311,7 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, space }) for i := range items { - res, err := h.itemToPropResponse(ctx, s, spaceID, refBase, pf, items[i]) + res, err := h.itemToPropResponse(ctx, s, u, pf, items[i], basePath) if err != nil { return nil, err } @@ -338,7 +333,7 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, space // itemToPropResponse needs to create a listing that contains a key and destination // the key is the name of an entry in the trash listing // for now we need to limit trash to the users home, so we can expect all trash keys to have the home storage as the opaque id -func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceID, refBase string, pf *propfind.XML, item *provider.RecycleItem) (*propfind.ResponseXML, error) { +func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, item *provider.RecycleItem, basePath string) (*responseXML, error) { baseURI := ctx.Value(net.CtxKeyBaseURI).(string) ref := path.Join(baseURI, refBase, item.Key) @@ -355,7 +350,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI t := utils.TSToTime(item.DeletionTime).UTC() dTime := t.Format(time.RFC1123Z) - size := strconv.FormatUint(item.Size, 10) + restorePath := strings.TrimPrefix(strings.TrimPrefix(item.Ref.Path, basePath), "/") // when allprops has been requested if pf.Allprop != nil { @@ -365,10 +360,10 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI Prop: []prop.PropertyXML{}, } // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-filename", path.Base(item.Ref.Path))) - propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) - propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) - propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-datetime", dTime)) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", restorePath)) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) // TODO(jfd): decide if we can and want to list oc:size for folders @@ -405,7 +400,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename - propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-location", restorePath)) case "trashbin-delete-datetime": propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-datetime", dTime)) case "trashbin-delete-timestamp": @@ -569,10 +564,10 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } info := dstStatRes.Info - w.Header().Set(net.HeaderContentType, info.MimeType) - w.Header().Set(net.HeaderETag, info.Etag) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(HeaderContentType, info.MimeType) + w.Header().Set(HeaderETag, info.Etag) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(HeaderOCETag, info.Etag) w.WriteHeader(successCode) } diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index 1506f82f25..4ae9c05efb 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -31,15 +31,12 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/rhttp" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rhttp" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/rs/zerolog" tusd "github.com/tus/tusd/pkg/handler" ) @@ -325,11 +322,11 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. isPublic, ) - w.Header().Set(net.HeaderContentType, info.MimeType) - w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(net.HeaderOCETag, info.Etag) - w.Header().Set(net.HeaderETag, info.Etag) - w.Header().Set(net.HeaderOCPermissions, permissions) + w.Header().Set(HeaderContentType, info.MimeType) + w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(HeaderETag, info.Etag) + w.Header().Set(HeaderOCPermissions, permissions) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index 9ebbab4c5a..d6fa35ee17 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -23,11 +23,8 @@ import ( "net/http" "path" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils/resourceid" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -57,8 +54,8 @@ func (h *VersionsHandler) Handler(s *svc, rid *provider.ResourceId) http.Handler } // baseURI is encoded as part of the response payload in href field - baseURI := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) - ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) + baseURI := path.Join(ctx.Value(ctxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) + ctx = context.WithValue(ctx, ctxKeyBaseURI, baseURI) r = r.WithContext(ctx) var key string diff --git a/internal/http/services/owncloud/ocdav/webdav.go b/internal/http/services/owncloud/ocdav/webdav.go index 98d4cc0ca8..83d001bf85 100644 --- a/internal/http/services/owncloud/ocdav/webdav.go +++ b/internal/http/services/owncloud/ocdav/webdav.go @@ -44,6 +44,46 @@ const ( MethodReport = "REPORT" ) +// Common HTTP headers. +const ( + HeaderAcceptRanges = "Accept-Ranges" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderContentDisposistion = "Content-Disposition" + HeaderContentLength = "Content-Length" + HeaderContentRange = "Content-Range" + HeaderContentType = "Content-Type" + HeaderETag = "ETag" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderRange = "Range" + HeaderIfMatch = "If-Match" +) + +// Non standard HTTP headers. +const ( + HeaderOCFileID = "OC-FileId" + HeaderOCETag = "OC-ETag" + HeaderOCChecksum = "OC-Checksum" + HeaderOCPermissions = "OC-Perm" + HeaderDepth = "Depth" + HeaderDav = "DAV" + HeaderTusResumable = "Tus-Resumable" + HeaderTusVersion = "Tus-Version" + HeaderTusExtension = "Tus-Extension" + HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" + HeaderTusUploadExpires = "Upload-Expires" + HeaderDestination = "Destination" + HeaderOverwrite = "Overwrite" + HeaderUploadChecksum = "Upload-Checksum" + HeaderUploadLength = "Upload-Length" + HeaderUploadMetadata = "Upload-Metadata" + HeaderUploadOffset = "Upload-Offset" + HeaderOCMtime = "X-OC-Mtime" + HeaderExpectedEntityLength = "X-Expected-Entity-Length" + HeaderTransferAuth = "TransferHeaderAuthorization" +) + // WebDavHandler implements a dav endpoint type WebDavHandler struct { namespace string diff --git a/internal/http/services/owncloud/ocs/cache.go b/internal/http/services/owncloud/ocs/cache.go index c83d4561f0..b2a9509b97 100644 --- a/internal/http/services/owncloud/ocs/cache.go +++ b/internal/http/services/owncloud/ocs/cache.go @@ -30,8 +30,12 @@ import ( func (s *svc) cacheWarmup(w http.ResponseWriter, r *http.Request) { if s.warmupCacheTracker != nil { - u := ctxpkg.ContextMustGetUser(r.Context()) - tkn := ctxpkg.ContextMustGetToken(r.Context()) + u, ok1 := ctxpkg.ContextGetUser(r.Context()) + tkn, ok2 := ctxpkg.ContextGetToken(r.Context()) + if !ok1 || !ok2 { + return + } + log := appctx.GetLogger(r.Context()) // We make a copy of the context because the original one comes with its cancel channel, diff --git a/internal/http/services/owncloud/ocs/config/config.go b/internal/http/services/owncloud/ocs/config/config.go index f5e1b92b82..041652aeba 100644 --- a/internal/http/services/owncloud/ocs/config/config.go +++ b/internal/http/services/owncloud/ocs/config/config.go @@ -25,22 +25,22 @@ import ( // Config holds the config options that need to be passed down to all ocs handlers type Config struct { - Prefix string `mapstructure:"prefix"` - Config data.ConfigData `mapstructure:"config"` - Capabilities data.CapabilitiesData `mapstructure:"capabilities"` - GatewaySvc string `mapstructure:"gatewaysvc"` - StorageregistrySvc string `mapstructure:"storage_registry_svc"` - DefaultUploadProtocol string `mapstructure:"default_upload_protocol"` - UserAgentChunkingMap map[string]string `mapstructure:"user_agent_chunking_map"` - SharePrefix string `mapstructure:"share_prefix"` - HomeNamespace string `mapstructure:"home_namespace"` - AdditionalInfoAttribute string `mapstructure:"additional_info_attribute"` - CacheWarmupDriver string `mapstructure:"cache_warmup_driver"` - CacheWarmupDrivers map[string]map[string]interface{} `mapstructure:"cache_warmup_drivers"` - ResourceInfoCacheSize int `mapstructure:"resource_info_cache_size"` - ResourceInfoCacheTTL int `mapstructure:"resource_info_cache_ttl"` - UserIdentifierCacheTTL int `mapstructure:"user_identifier_cache_ttl"` - MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"` + Prefix string `mapstructure:"prefix"` + Config data.ConfigData `mapstructure:"config"` + Capabilities data.CapabilitiesData `mapstructure:"capabilities"` + GatewaySvc string `mapstructure:"gatewaysvc"` + StorageregistrySvc string `mapstructure:"storage_registry_svc"` + DefaultUploadProtocol string `mapstructure:"default_upload_protocol"` + UserAgentChunkingMap map[string]string `mapstructure:"user_agent_chunking_map"` + SharePrefix string `mapstructure:"share_prefix"` + HomeNamespace string `mapstructure:"home_namespace"` + AdditionalInfoAttribute string `mapstructure:"additional_info_attribute"` + CacheWarmupDriver string `mapstructure:"cache_warmup_driver"` + CacheWarmupDrivers map[string]map[string]interface{} `mapstructure:"cache_warmup_drivers"` + ResourceInfoCacheDriver string `mapstructure:"resource_info_cache_type"` + ResourceInfoCacheTTL int `mapstructure:"resource_info_cache_ttl"` + ResourceInfoCacheDrivers map[string]map[string]interface{} `mapstructure:"resource_info_caches"` + UserIdentifierCacheTTL int `mapstructure:"user_identifier_cache_ttl"` } // Init sets sane defaults @@ -65,10 +65,6 @@ func (c *Config) Init() { c.AdditionalInfoAttribute = "{{.Mail}}" } - if c.ResourceInfoCacheSize == 0 { - c.ResourceInfoCacheSize = 1000000 - } - if c.UserIdentifierCacheTTL == 0 { c.UserIdentifierCacheTTL = 60 } diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index 9abf695cec..376dde81fa 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -302,7 +302,7 @@ func timestampToExpiration(t *types.Timestamp) string { // ParseTimestamp tries to parses the ocs expiry into a CS3 Timestamp func ParseTimestamp(timestampString string) (*types.Timestamp, error) { - parsedTime, err := time.Parse("2006-01-02T15:04:05Z0700", timestampString) + parsedTime, err := time.Parse(time.RFC3339, timestampString) if err != nil { parsedTime, err = time.Parse("2006-01-02", timestampString) } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go index 9445937334..60255ccc31 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go @@ -59,7 +59,7 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting gateway grpc client", err) return } - usersRes, err := gwc.FindUsers(r.Context(), &userpb.FindUsersRequest{Filter: term}) + usersRes, err := gwc.FindUsers(r.Context(), &userpb.FindUsersRequest{Filter: term, SkipFetchingUserGroups: true}) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching users", err) return @@ -73,7 +73,7 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) { userMatches = append(userMatches, match) } - groupsRes, err := gwc.FindGroups(r.Context(), &grouppb.FindGroupsRequest{Filter: term}) + groupsRes, err := gwc.FindGroups(r.Context(), &grouppb.FindGroupsRequest{Filter: term, SkipFetchingMembers: true}) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching groups", err) return diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go index 8dab153723..3a5d6a69c8 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go @@ -51,8 +51,9 @@ func (h *Handler) createGroupShare(w http.ResponseWriter, r *http.Request, statI } groupRes, err := c.GetGroupByClaim(ctx, &grouppb.GetGroupByClaimRequest{ - Claim: "group_name", - Value: shareWith, + Claim: "group_name", + Value: shareWith, + SkipFetchingMembers: true, }) if err != nil { return nil, &ocsError{ diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 12d0a8a8f3..2b99565782 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -44,20 +44,19 @@ import ( "google.golang.org/protobuf/types/known/fieldmaskpb" "github.com/ReneKroon/ttlcache/v2" - "github.com/bluele/gcache" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/config" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/response" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/publicshare" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/share" - "github.com/cs3org/reva/v2/pkg/share/cache" - "github.com/cs3org/reva/v2/pkg/share/cache/registry" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/publicshare" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/share" + "github.com/cs3org/reva/pkg/share/cache" + cachereg "github.com/cs3org/reva/pkg/share/cache/registry" + warmupreg "github.com/cs3org/reva/pkg/share/cache/warmup/registry" + "github.com/cs3org/reva/pkg/utils" + "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/pkg/errors" ) @@ -79,7 +78,7 @@ type Handler struct { homeNamespace string additionalInfoTemplate *template.Template userIdentifierCache *ttlcache.Cache - resourceInfoCache gcache.Cache + resourceInfoCache cache.ResourceInfoCache resourceInfoCacheTTL time.Duration getClient GatewayClientGetter @@ -99,16 +98,20 @@ type ocsError struct { } func getCacheWarmupManager(c *config.Config) (cache.Warmup, error) { - if f, ok := registry.NewFuncs[c.CacheWarmupDriver]; ok { + if f, ok := warmupreg.NewFuncs[c.CacheWarmupDriver]; ok { return f(c.CacheWarmupDrivers[c.CacheWarmupDriver]) } return nil, fmt.Errorf("driver not found: %s", c.CacheWarmupDriver) } -// GatewayClientGetter is the function being used to retrieve a gateway client instance -type GatewayClientGetter func() (gateway.GatewayAPIClient, error) +func getCacheManager(c *config.Config) (cache.ResourceInfoCache, error) { + if f, ok := cachereg.NewFuncs[c.ResourceInfoCacheDriver]; ok { + return f(c.ResourceInfoCacheDrivers[c.ResourceInfoCacheDriver]) + } + return nil, fmt.Errorf("driver not found: %s", c.ResourceInfoCacheDriver) +} -// Init initializes the handler using default values +// Init initializes this and any contained handlers func (h *Handler) Init(c *config.Config) { h.gatewayAddr = c.GatewaySvc h.machineAuthAPIKey = c.MachineAuthAPIKey @@ -116,14 +119,18 @@ func (h *Handler) Init(c *config.Config) { h.publicURL = c.Config.Host h.sharePrefix = c.SharePrefix h.homeNamespace = c.HomeNamespace - h.resourceInfoCache = gcache.New(c.ResourceInfoCacheSize).LFU().Build() - h.resourceInfoCacheTTL = time.Second * time.Duration(c.ResourceInfoCacheTTL) h.additionalInfoTemplate, _ = template.New("additionalInfo").Parse(c.AdditionalInfoAttribute) + h.resourceInfoCacheTTL = time.Second * time.Duration(c.ResourceInfoCacheTTL) h.userIdentifierCache = ttlcache.NewCache() _ = h.userIdentifierCache.SetTTL(time.Second * time.Duration(c.UserIdentifierCacheTTL)) + cache, err := getCacheManager(c) + if err == nil { + h.resourceInfoCache = cache + } + if h.resourceInfoCacheTTL > 0 { cwm, err := getCacheWarmupManager(c) if err == nil { @@ -1109,6 +1116,7 @@ func (h *Handler) mustGetIdentifiers(ctx context.Context, client gateway.Gateway GroupId: &grouppb.GroupId{ OpaqueId: id, }, + SkipFetchingMembers: true, }) if err != nil { sublog.Err(err).Msg("could not look up group") @@ -1138,6 +1146,7 @@ func (h *Handler) mustGetIdentifiers(ctx context.Context, client gateway.Gateway UserId: &userpb.UserId{ OpaqueId: id, }, + SkipFetchingUserGroups: true, }) if err != nil { sublog.Err(err).Msg("could not look up user") @@ -1229,7 +1238,7 @@ func (h *Handler) getResourceInfoByReference(ctx context.Context, client gateway } func (h *Handler) getResourceInfoByID(ctx context.Context, client gateway.GatewayAPIClient, id *provider.ResourceId) (*provider.ResourceInfo, *rpc.Status, error) { - return h.getResourceInfo(ctx, client, resourceid.OwnCloudResourceIDWrap(id), &provider.Reference{ResourceId: id, Path: "."}) + return h.getResourceInfo(ctx, client, resourceid.OwnCloudResourceIDWrap(id), &provider.Reference{ResourceId: id}) } // getResourceInfo retrieves the resource info to a target. @@ -1239,11 +1248,16 @@ func (h *Handler) getResourceInfo(ctx context.Context, client gateway.GatewayAPI var pinfo *provider.ResourceInfo var status *rpc.Status - if infoIf, err := h.resourceInfoCache.Get(key); h.resourceInfoCacheTTL > 0 && err == nil { - logger.Debug().Msgf("cache hit for resource %+v", key) - pinfo = infoIf.(*provider.ResourceInfo) - status = &rpc.Status{Code: rpc.Code_CODE_OK} - } else { + var err error + var foundInCache bool + if h.resourceInfoCacheTTL > 0 && h.resourceInfoCache != nil { + if pinfo, err = h.resourceInfoCache.Get(key); err == nil { + logger.Debug().Msgf("cache hit for resource %+v", key) + status = &rpc.Status{Code: rpc.Code_CODE_OK} + foundInCache = true + } + } + if !foundInCache { logger.Debug().Msgf("cache miss for resource %+v, statting", key) statReq := &provider.StatRequest{ Ref: ref, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 3f326e0b83..335e0b9448 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -53,8 +53,9 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn } userRes, err := c.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: shareWith, + Claim: "username", + Value: shareWith, + SkipFetchingUserGroups: true, }) if err != nil { return nil, &ocsError{ diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 38894e08d1..3457ffb030 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -49,7 +49,22 @@ func (h *Handler) Init(c *config.Config) { // GetGroups handles GET requests on /cloud/users/groups // TODO: implement func (h *Handler) GetGroups(w http.ResponseWriter, r *http.Request) { - response.WriteOCSSuccess(w, r, &Groups{}) + ctx := r.Context() + + user := chi.URLParam(r, "userid") + // FIXME use ldap to fetch user info + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing user in context", fmt.Errorf("missing user in context")) + return + } + if user != u.Username { + // FIXME allow fetching other users info? only for admins + response.WriteOCSError(w, r, http.StatusForbidden, "user id mismatch", fmt.Errorf("%s tried to access %s user info endpoint", u.Id.OpaqueId, user)) + return + } + + response.WriteOCSSuccess(w, r, &Groups{Groups: u.Groups}) } // Quota holds quota information @@ -139,12 +154,9 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { var total, used uint64 var relative float32 - // lightweight accounts don't have access to their storage space - if u.Id.Type != userpb.UserType_USER_TYPE_LIGHTWEIGHT { - getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{ - ResourceId: res.StorageSpaces[0].Root, - Path: ".", - }}) + // lightweight and federated accounts don't have access to their storage space + if u.Id.Type != userpb.UserType_USER_TYPE_LIGHTWEIGHT && u.Id.Type != userpb.UserType_USER_TYPE_FEDERATED { + getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{Path: getHomeRes.Path}}) if err != nil { sublog.Error().Err(err).Msg("error calling GetQuota") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocs/ocs.go b/internal/http/services/owncloud/ocs/ocs.go index f35659743d..4fec65852a 100644 --- a/internal/http/services/owncloud/ocs/ocs.go +++ b/internal/http/services/owncloud/ocs/ocs.go @@ -84,12 +84,7 @@ func (s *svc) Close() error { } func (s *svc) Unprotected() []string { - return []string{ - "/v1.php/config", - "/v2.php/config", - "/v1.php/apps/files_sharing/api/v1/tokeninfo/unprotected", - "/v2.php/apps/files_sharing/api/v1/tokeninfo/unprotected", - } + return []string{"/v1.php/cloud/capabilities", "/v2.php/cloud/capabilities"} } func (s *svc) routerInit() error { diff --git a/internal/http/services/preferences/preferences.go b/internal/http/services/preferences/preferences.go new file mode 100644 index 0000000000..f0127a160a --- /dev/null +++ b/internal/http/services/preferences/preferences.go @@ -0,0 +1,212 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package preferences + +import ( + "encoding/json" + "net/http" + + preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/go-chi/chi/v5" + "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" +) + +func init() { + global.Register("preferences", New) +} + +// Config holds the config options that for the preferences HTTP service +type Config struct { + Prefix string `mapstructure:"prefix"` + GatewaySvc string `mapstructure:"gatewaysvc"` +} + +func (c *Config) init() { + if c.Prefix == "" { + c.Prefix = "preferences" + } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) +} + +type svc struct { + conf *Config + router *chi.Mux +} + +// New returns a new ocmd object +func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) { + + conf := &Config{} + if err := mapstructure.Decode(m, conf); err != nil { + return nil, err + } + conf.init() + + r := chi.NewRouter() + s := &svc{ + conf: conf, + router: r, + } + + if err := s.routerInit(); err != nil { + return nil, err + } + + return s, nil +} + +func (s *svc) routerInit() error { + s.router.Get("/", s.handleGet) + s.router.Post("/", s.handlePost) + return nil +} + +// Close performs cleanup. +func (s *svc) Close() error { + return nil +} + +func (s *svc) Prefix() string { + return s.conf.Prefix +} + +func (s *svc) Unprotected() []string { + return []string{} +} + +func (s *svc) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.router.ServeHTTP(w, r) + }) +} + +func (s *svc) handleGet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + key := r.URL.Query().Get("key") + ns := r.URL.Query().Get("ns") + + if key == "" || ns == "" { + w.WriteHeader(http.StatusBadRequest) + if _, err := w.Write([]byte("key or namespace query missing")); err != nil { + log.Error().Err(err).Msg("error writing to response") + w.WriteHeader(http.StatusInternalServerError) + } + return + + } + + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + log.Error().Err(err).Msg("error getting grpc gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + res, err := client.GetKey(ctx, &preferences.GetKeyRequest{ + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, + }) + if err != nil { + log.Error().Err(err).Msg("error retrieving key") + w.WriteHeader(http.StatusInternalServerError) + return + } + if res.Status.Code != rpc.Code_CODE_OK { + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + log.Error().Interface("status", res.Status).Msg("error retrieving key") + return + } + + js, err := json.Marshal(map[string]interface{}{ + "namespace": ns, + "key": key, + "value": res.Val, + }) + if err != nil { + log.Error().Err(err).Msg("error marshalling response") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err = w.Write(js); err != nil { + log.Error().Err(err).Msg("error writing JSON response") + w.WriteHeader(http.StatusInternalServerError) + return + } + +} + +func (s *svc) handlePost(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + key := r.FormValue("key") + ns := r.FormValue("ns") + val := r.FormValue("value") + + if key == "" || ns == "" || val == "" { + w.WriteHeader(http.StatusBadRequest) + if _, err := w.Write([]byte("key, namespace or value parameter missing")); err != nil { + log.Error().Err(err).Msg("error writing to response") + w.WriteHeader(http.StatusInternalServerError) + } + return + + } + + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + log.Error().Err(err).Msg("error getting grpc gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + res, err := client.SetKey(ctx, &preferences.SetKeyRequest{ + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, + Val: val, + }) + if err != nil { + log.Error().Err(err).Msg("error setting key") + w.WriteHeader(http.StatusInternalServerError) + return + } + if res.Status.Code != rpc.Code_CODE_OK { + w.WriteHeader(http.StatusInternalServerError) + log.Error().Interface("status", res.Status).Msg("error setting key") + return + } +} diff --git a/pkg/app/provider/wopi/wopi.go b/pkg/app/provider/wopi/wopi.go index b01e15382b..4b57b799d5 100644 --- a/pkg/app/provider/wopi/wopi.go +++ b/pkg/app/provider/wopi/wopi.go @@ -140,7 +140,7 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc u, ok := ctxpkg.ContextGetUser(ctx) if ok { // else defaults to "Guest xyz" - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { q.Add("userid", resource.Owner.OpaqueId+"@"+resource.Owner.Idp) } else { q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp) @@ -154,7 +154,6 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc if !isPublicShare { q.Add("username", u.Username) - q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp) } } @@ -166,13 +165,19 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc q.Add("appviewurl", viewAppURL) } } - if editAppURLs, ok := p.appURLs["edit"]; ok { + var access string = "edit" + if resource.GetSize() == 0 { + if _, ok := p.appURLs["editnew"]; ok { + access = "editnew" + } + } + if editAppURLs, ok := p.appURLs[access]; ok { if editAppURL, ok := editAppURLs[ext]; ok { q.Add("appurl", editAppURL) } } if q.Get("appurl") == "" { - // assuming that an view action is always available in the /hosting/discovery manifest + // assuming that a view action is always available in the /hosting/discovery manifest // eg. Collabora does support viewing jpgs but no editing // eg. OnlyOffice does support viewing pdfs but no editing // there is no known case of supporting edit only without view @@ -328,6 +333,7 @@ func getAppURLs(c *config) (map[string]map[string]string, error) { } // register the supported mimetypes in the AppRegistry: this is hardcoded for the time being + // TODO(lopresti) move to config switch c.AppName { case "CodiMD": appURLs = getCodimdExtensions(c.AppURL) @@ -372,7 +378,7 @@ func parseWopiDiscovery(body io.Reader) (map[string]map[string]string, error) { for _, app := range netzone.SelectElements("app") { for _, action := range app.SelectElements("action") { access := action.SelectAttrValue("name", "") - if access == "view" || access == "edit" { + if access == "view" || access == "edit" || access == "editnew" { ext := action.SelectAttrValue("ext", "") urlString := action.SelectAttrValue("urlsrc", "") diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index a8c10f4b59..98bc4355c0 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -62,7 +62,7 @@ func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret strin if c.Secret == clientSecret { var scopes map[string]*authpb.Scope var err error - if c.User.Id != nil && c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if c.User.Id != nil && (c.User.Id.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || c.User.Id.Type == user.UserType_USER_TYPE_FEDERATED) { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index 267fc09b7a..150014ba8c 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -117,7 +117,7 @@ func (m *manager) Authenticate(ctx context.Context, username string, secret stri if c.Secret == secret { var scopes map[string]*authpb.Scope var err error - if c.ID != nil && c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if c.ID != nil && (c.ID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || c.ID.Type == user.UserType_USER_TYPE_FEDERATED) { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/manager/loader/loader.go b/pkg/auth/manager/loader/loader.go index b4737b62a0..69862bc144 100644 --- a/pkg/auth/manager/loader/loader.go +++ b/pkg/auth/manager/loader/loader.go @@ -20,16 +20,15 @@ package loader import ( // Load core authentication managers. - _ "github.com/cs3org/reva/v2/pkg/auth/manager/appauth" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/demo" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/impersonator" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/json" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/ldap" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/machine" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/nextcloud" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/oidc" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/oidcmapping" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql" - _ "github.com/cs3org/reva/v2/pkg/auth/manager/publicshares" + _ "github.com/cs3org/reva/pkg/auth/manager/appauth" + _ "github.com/cs3org/reva/pkg/auth/manager/demo" + _ "github.com/cs3org/reva/pkg/auth/manager/impersonator" + _ "github.com/cs3org/reva/pkg/auth/manager/json" + _ "github.com/cs3org/reva/pkg/auth/manager/ldap" + _ "github.com/cs3org/reva/pkg/auth/manager/machine" + _ "github.com/cs3org/reva/pkg/auth/manager/nextcloud" + _ "github.com/cs3org/reva/pkg/auth/manager/oidc" + _ "github.com/cs3org/reva/pkg/auth/manager/owncloudsql" + _ "github.com/cs3org/reva/pkg/auth/manager/publicshares" // Add your own here ) diff --git a/pkg/auth/manager/nextcloud/nextcloud_test.go b/pkg/auth/manager/nextcloud/nextcloud_test.go index 95e7f3ac2e..ce10328843 100644 --- a/pkg/auth/manager/nextcloud/nextcloud_test.go +++ b/pkg/auth/manager/nextcloud/nextcloud_test.go @@ -29,10 +29,10 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth/manager/nextcloud" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - jwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/auth/manager/nextcloud" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index 6eba498cfa..ec5b67be47 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -22,7 +22,9 @@ package oidc import ( "context" + "encoding/json" "fmt" + "io/ioutil" "strings" "time" @@ -30,15 +32,18 @@ import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth" - "github.com/cs3org/reva/v2/pkg/auth/manager/registry" - "github.com/cs3org/reva/v2/pkg/auth/scope" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/rhttp" - "github.com/cs3org/reva/v2/pkg/sharedconf" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth" + "github.com/cs3org/reva/pkg/auth/manager/registry" + "github.com/cs3org/reva/pkg/auth/scope" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/juliangruber/go-intersect" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" - "github.com/rs/zerolog/log" "golang.org/x/oauth2" ) @@ -47,17 +52,26 @@ func init() { } type mgr struct { - provider *oidc.Provider // cached on first request - c *config + provider *oidc.Provider // cached on first request + c *config + oidcUsersMapping map[string]*oidcUserMapping } type config struct { - Insecure bool `mapstructure:"insecure" docs:"false;Whether to skip certificate checks when sending requests."` - Issuer string `mapstructure:"issuer" docs:";The issuer of the OIDC token."` - IDClaim string `mapstructure:"id_claim" docs:"sub;The claim containing the ID of the user."` - UIDClaim string `mapstructure:"uid_claim" docs:";The claim containing the UID of the user."` - GIDClaim string `mapstructure:"gid_claim" docs:";The claim containing the GID of the user."` - GatewaySvc string `mapstructure:"gatewaysvc" docs:";The endpoint at which the GRPC gateway is exposed."` + Insecure bool `mapstructure:"insecure" docs:"false;Whether to skip certificate checks when sending requests."` + Issuer string `mapstructure:"issuer" docs:";The issuer of the OIDC token."` + IDClaim string `mapstructure:"id_claim" docs:"sub;The claim containing the ID of the user."` + UIDClaim string `mapstructure:"uid_claim" docs:";The claim containing the UID of the user."` + GIDClaim string `mapstructure:"gid_claim" docs:";The claim containing the GID of the user."` + GatewaySvc string `mapstructure:"gatewaysvc" docs:";The endpoint at which the GRPC gateway is exposed."` + UsersMapping string `mapstructure:"users_mapping" docs:"; The optional OIDC users mapping file path"` + GroupClaim string `mapstructure:"group_claim" docs:"; The group claim to be looked up to map the user (default to 'groups')."` +} + +type oidcUserMapping struct { + OIDCIssuer string `mapstructure:"oidc_issuer" json:"oidc_issuer"` + OIDCGroup string `mapstructure:"oidc_group" json:"oidc_group"` + Username string `mapstructure:"username" json:"username"` } func (c *config) init() { @@ -65,6 +79,9 @@ func (c *config) init() { // sub is stable and defined as unique. the user manager needs to take care of the sub to user metadata lookup c.IDClaim = "sub" } + if c.GroupClaim == "" { + c.GroupClaim = "groups" + } c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) } @@ -95,38 +112,67 @@ func (am *mgr) Configure(m map[string]interface{}) error { } c.init() am.c = c + + am.oidcUsersMapping = map[string]*oidcUserMapping{} + if c.UsersMapping == "" { + // no mapping defined, leave the map empty and move on + return nil + } + + f, err := ioutil.ReadFile(c.UsersMapping) + if err != nil { + return fmt.Errorf("oidc: error reading the users mapping file: +%v", err) + } + oidcUsers := []*oidcUserMapping{} + err = json.Unmarshal(f, &oidcUsers) + if err != nil { + return fmt.Errorf("oidc: error unmarshalling the users mapping file: +%v", err) + } + for _, u := range oidcUsers { + if _, found := am.oidcUsersMapping[u.OIDCGroup]; found { + return fmt.Errorf("oidc: mapping error, group \"%s\" is mapped to multiple users", u.OIDCGroup) + } + am.oidcUsersMapping[u.OIDCGroup] = u + } + return nil } -// the clientID it would be empty as we only need to validate the clientSecret variable +// The clientID would be empty as we only need to validate the clientSecret variable // which contains the access token that we can use to contact the UserInfo endpoint // and get the user claims. func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { ctx = am.getOAuthCtx(ctx) + log := appctx.GetLogger(ctx) oidcProvider, err := am.getOIDCProvider(ctx) if err != nil { - return nil, nil, fmt.Errorf("error creating oidc provider: +%v", err) + return nil, nil, fmt.Errorf("oidc: error creating oidc provider: +%v", err) } oauth2Token := &oauth2.Token{ AccessToken: clientSecret, } + + // query the oidc provider for user info userInfo, err := oidcProvider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token)) if err != nil { return nil, nil, fmt.Errorf("oidc: error getting userinfo: +%v", err) } - // claims contains the standard OIDC claims like issuer, iat, aud, ... and any other non-standard one. + // claims contains the standard OIDC claims like iss, iat, aud, ... and any other non-standard one. // TODO(labkode): make claims configuration dynamic from the config file so we can add arbitrary mappings from claims to user struct. + // For now, only the group claim is dynamic. + // TODO(labkode): may do like K8s does it: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go var claims map[string]interface{} if err := userInfo.Claims(&claims); err != nil { return nil, nil, fmt.Errorf("oidc: error unmarshaling userinfo claims: %v", err) } + log.Debug().Interface("claims", claims).Interface("userInfo", userInfo).Msg("unmarshalled userinfo") - if claims["issuer"] == nil { // This is not set in simplesamlphp - claims["issuer"] = am.c.Issuer + if claims["iss"] == nil { // This is not set in simplesamlphp + claims["iss"] = am.c.Issuer } if claims["email_verified"] == nil { // This is not set in simplesamlphp claims["email_verified"] = false @@ -134,39 +180,30 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) if claims["preferred_username"] == nil { claims["preferred_username"] = claims[am.c.IDClaim] } + if claims["preferred_username"] == nil { + claims["preferred_username"] = claims["email"] + } if claims["name"] == nil { claims["name"] = claims[am.c.IDClaim] } - - if claims["email"] == nil { - return nil, nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope") - } - - userClaim := "preferred_username" - if claims["preferred_username"] == nil { - if claims["email"] != nil { - userClaim = "email" - } else { - return nil, nil, fmt.Errorf("no \"preferred_username\" and \"email\" attribute found in userinfo: maybe the client did not request the oidc \"profile\"-scope") - } - } if claims["name"] == nil { return nil, nil, fmt.Errorf("no \"name\" attribute found in userinfo: maybe the client did not request the oidc \"profile\"-scope") } - - var uid, gid float64 - if am.c.UIDClaim != "" { - uid, _ = claims[am.c.UIDClaim].(float64) + if claims["email"] == nil { + return nil, nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope") } - if am.c.GIDClaim != "" { - gid, _ = claims[am.c.GIDClaim].(float64) + + err = am.resolveUser(ctx, claims) + if err != nil { + return nil, nil, errors.Wrapf(err, "oidc: error resolving username for external user '%v'", claims["email"]) } userID := &user.UserId{ OpaqueId: claims[am.c.IDClaim].(string), // a stable non reassignable id - Idp: claims["issuer"].(string), // in the scope of this issuer + Idp: claims["iss"].(string), // in the scope of this issuer Type: getUserType(claims[am.c.IDClaim].(string)), } + gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { return nil, nil, errors.Wrap(err, "oidc: error getting gateway grpc client") @@ -175,29 +212,33 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) UserId: userID, }) if err != nil { - return nil, nil, errors.Wrap(err, "oidc: error getting user groups") + return nil, nil, errors.Wrapf(err, "oidc: error getting user groups for '%+v'", userID) } if getGroupsResp.Status.Code != rpc.Code_CODE_OK { - return nil, nil, errors.Wrap(err, "oidc: grpc getting user groups failed") + return nil, nil, status.NewErrorFromCode(getGroupsResp.Status.Code, "oidc") + } + + var uid, gid int64 + if am.c.UIDClaim != "" { + uid, _ = claims[am.c.UIDClaim].(int64) + } + if am.c.GIDClaim != "" { + gid, _ = claims[am.c.GIDClaim].(int64) } u := &user.User{ - Id: userID, - Username: claims[userClaim].(string), - // TODO(labkode) if we can get groups from the claim we need to give the possibility - // to the admin to choose what claim provides the groups. - // TODO(labkode) ... use all claims from oidc? - // TODO(labkode): do like K8s does it: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go + Id: userID, + Username: claims["preferred_username"].(string), Groups: getGroupsResp.Groups, Mail: claims["email"].(string), MailVerified: claims["email_verified"].(bool), DisplayName: claims["name"].(string), - UidNumber: int64(uid), - GidNumber: int64(gid), + UidNumber: uid, + GidNumber: gid, } var scopes map[string]*authpb.Scope - if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if userID != nil && (userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || userID.Type == user.UserType_USER_TYPE_FEDERATED) { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err @@ -226,24 +267,88 @@ func (am *mgr) getOAuthCtx(ctx context.Context) context.Context { return ctx } +// getOIDCProvider returns a singleton OIDC provider func (am *mgr) getOIDCProvider(ctx context.Context) (*oidc.Provider, error) { + ctx = am.getOAuthCtx(ctx) + log := appctx.GetLogger(ctx) + if am.provider != nil { return am.provider, nil } // Initialize a provider by specifying the issuer URL. - // Once initialized is a singleton that is reused if further requests. + // Once initialized this is a singleton that is reused for further requests. // The provider is responsible to verify the token sent by the client // against the security keys oftentimes available in the .well-known endpoint. provider, err := oidc.NewProvider(ctx, am.c.Issuer) + if err != nil { - return nil, fmt.Errorf("error creating a new oidc provider: %+v", err) + log.Error().Err(err).Msg("oidc: error creating a new oidc provider") + return nil, fmt.Errorf("oidc: error creating a new oidc provider: %+v", err) } am.provider = provider return am.provider, nil } +func (am *mgr) resolveUser(ctx context.Context, claims map[string]interface{}) error { + if len(am.oidcUsersMapping) > 0 { + var username string + + // map and discover the user's username when a mapping is defined + if claims[am.c.GroupClaim] == nil { + // we are required to perform a user mapping but the group claim is not available + return fmt.Errorf("no \"%s\" claim found in userinfo to map user", am.c.GroupClaim) + } + mappings := make([]string, 0, len(am.oidcUsersMapping)) + for _, m := range am.oidcUsersMapping { + if m.OIDCIssuer == claims["iss"] { + mappings = append(mappings, m.OIDCGroup) + } + } + + intersection := intersect.Simple(claims[am.c.GroupClaim], mappings) + if len(intersection) > 1 { + // multiple mappings are not implemented as we cannot decide which one to choose + return errtypes.PermissionDenied("more than one user mapping entry exists for the given group claims") + } + if len(intersection) == 0 { + return errtypes.PermissionDenied("no user mapping found for the given group claim(s)") + } + for _, m := range intersection { + username = am.oidcUsersMapping[m.(string)].Username + } + + upsc, err := pool.GetUserProviderServiceClient(am.c.GatewaySvc) + if err != nil { + return errors.Wrap(err, "error getting user provider grpc client") + } + getUserByClaimResp, err := upsc.GetUserByClaim(ctx, &user.GetUserByClaimRequest{ + Claim: "username", + Value: username, + }) + if err != nil { + return errors.Wrapf(err, "error getting user by username '%v'", username) + } + if getUserByClaimResp.Status.Code != rpc.Code_CODE_OK { + return status.NewErrorFromCode(getUserByClaimResp.Status.Code, "oidc") + } + + // take the properties of the mapped target user to override the claims + claims["preferred_username"] = username + claims[am.c.IDClaim] = getUserByClaimResp.GetUser().GetId().OpaqueId + claims["iss"] = getUserByClaimResp.GetUser().GetId().Idp + if am.c.UIDClaim != "" { + claims[am.c.UIDClaim] = getUserByClaimResp.GetUser().UidNumber + } + if am.c.GIDClaim != "" { + claims[am.c.GIDClaim] = getUserByClaimResp.GetUser().GidNumber + } + appctx.GetLogger(ctx).Debug().Str("username", username).Interface("claims", claims).Msg("resolveUser: claims overridden from mapped user") + } + return nil +} + func getUserType(upn string) user.UserType { var t user.UserType switch { @@ -255,5 +360,4 @@ func getUserType(upn string) user.UserType { t = user.UserType_USER_TYPE_PRIMARY } return t - } diff --git a/pkg/auth/manager/owncloudsql/owncloudsql.go b/pkg/auth/manager/owncloudsql/owncloudsql.go index b2846462ea..e4e7ded319 100644 --- a/pkg/auth/manager/owncloudsql/owncloudsql.go +++ b/pkg/auth/manager/owncloudsql/owncloudsql.go @@ -144,7 +144,7 @@ func (m *manager) Authenticate(ctx context.Context, login, clientSecret string) } var scopes map[string]*authpb.Scope - if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { + if userID != nil && (userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || userID.Type == user.UserType_USER_TYPE_FEDERATED) { scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) if err != nil { return nil, nil, err diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index b303e07f78..5ff0162417 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -93,8 +93,6 @@ func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interfa return checkStorageRef(ctx, &share, &provider.Reference{ResourceId: v.ResourceInfo.Id}), nil case *gateway.OpenInAppRequest: return checkStorageRef(ctx, &share, v.GetRef()), nil - case *permissionsv1beta1.CheckPermissionRequest: - return true, nil // Editor role // need to return appropriate status codes in the ocs/ocdav layers. diff --git a/pkg/cbox/group/rest/rest.go b/pkg/cbox/group/rest/rest.go index 375bdc9a9c..eba765c17e 100644 --- a/pkg/cbox/group/rest/rest.go +++ b/pkg/cbox/group/rest/rest.go @@ -192,7 +192,7 @@ func (m *manager) parseAndCacheGroup(ctx context.Context, groupData map[string]i } -func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb.Group, error) { +func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) { g, err := m.fetchCachedGroupDetails(gid) if err != nil { groupData, err := m.getGroupByParam(ctx, "groupIdentifier", gid.OpaqueId) @@ -202,20 +202,22 @@ func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb. g = m.parseAndCacheGroup(ctx, groupData) } - groupMembers, err := m.GetMembers(ctx, gid) - if err != nil { - return nil, err + if !skipFetchingMembers { + groupMembers, err := m.GetMembers(ctx, gid) + if err != nil { + return nil, err + } + g.Members = groupMembers } - g.Members = groupMembers return g, nil } -func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*grouppb.Group, error) { +func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) { value = url.QueryEscape(value) opaqueID, err := m.fetchCachedParam(claim, value) if err == nil { - return m.GetGroup(ctx, &grouppb.GroupId{OpaqueId: opaqueID}) + return m.GetGroup(ctx, &grouppb.GroupId{OpaqueId: opaqueID}, skipFetchingMembers) } switch claim { @@ -236,17 +238,19 @@ func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*gr } g := m.parseAndCacheGroup(ctx, groupData) - groupMembers, err := m.GetMembers(ctx, g.Id) - if err != nil { - return nil, err + if !skipFetchingMembers { + groupMembers, err := m.GetMembers(ctx, g.Id) + if err != nil { + return nil, err + } + g.Members = groupMembers } - g.Members = groupMembers return g, nil } -func (m *manager) findGroupsByFilter(ctx context.Context, url string, groups map[string]*grouppb.Group) error { +func (m *manager) findGroupsByFilter(ctx context.Context, url string, groups map[string]*grouppb.Group, skipFetchingMembers bool) error { groupData, err := m.apiTokenManager.SendAPIGetRequest(ctx, url, false) if err != nil { @@ -265,23 +269,33 @@ func (m *manager) findGroupsByFilter(ctx context.Context, url string, groups map OpaqueId: id, Idp: m.conf.IDProvider, } + + var groupMembers []*userpb.UserId + if !skipFetchingMembers { + groupMembers, err = m.GetMembers(ctx, groupID) + if err != nil { + return err + } + } gid, ok := grpInfo["gid"].(int64) if !ok { gid = 0 } + groups[groupID.OpaqueId] = &grouppb.Group{ Id: groupID, GroupName: id, Mail: id + "@cern.ch", DisplayName: name, GidNumber: gid, + Members: groupMembers, } } return nil } -func (m *manager) FindGroups(ctx context.Context, query string) ([]*grouppb.Group, error) { +func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) { // Look at namespaces filters. If the query starts with: // "a" or none => get egroups @@ -308,7 +322,7 @@ func (m *manager) FindGroups(ctx context.Context, query string) ([]*grouppb.Grou for _, f := range filters { url := fmt.Sprintf("%s/Group/?filter=%s:contains:%s&field=groupIdentifier&field=displayName&field=gid", m.conf.APIBaseURL, f, url.QueryEscape(query)) - err := m.findGroupsByFilter(ctx, url, groups) + err := m.findGroupsByFilter(ctx, url, groups, skipFetchingMembers) if err != nil { return nil, err } diff --git a/pkg/cbox/loader/loader.go b/pkg/cbox/loader/loader.go index a481d08907..1e5baa9d0f 100644 --- a/pkg/cbox/loader/loader.go +++ b/pkg/cbox/loader/loader.go @@ -20,11 +20,12 @@ package loader import ( // Load cbox specific drivers. - _ "github.com/cs3org/reva/v2/pkg/cbox/favorite/sql" - _ "github.com/cs3org/reva/v2/pkg/cbox/group/rest" - _ "github.com/cs3org/reva/v2/pkg/cbox/publicshare/sql" - _ "github.com/cs3org/reva/v2/pkg/cbox/share/sql" - _ "github.com/cs3org/reva/v2/pkg/cbox/storage/eoshomewrapper" - _ "github.com/cs3org/reva/v2/pkg/cbox/storage/eoswrapper" - _ "github.com/cs3org/reva/v2/pkg/cbox/user/rest" + _ "github.com/cs3org/reva/pkg/cbox/favorite/sql" + _ "github.com/cs3org/reva/pkg/cbox/group/rest" + _ "github.com/cs3org/reva/pkg/cbox/preferences/sql" + _ "github.com/cs3org/reva/pkg/cbox/publicshare/sql" + _ "github.com/cs3org/reva/pkg/cbox/share/sql" + _ "github.com/cs3org/reva/pkg/cbox/storage/eoshomewrapper" + _ "github.com/cs3org/reva/pkg/cbox/storage/eoswrapper" + _ "github.com/cs3org/reva/pkg/cbox/user/rest" ) diff --git a/pkg/cbox/preferences/sql/sql.go b/pkg/cbox/preferences/sql/sql.go new file mode 100644 index 0000000000..67ba2ad731 --- /dev/null +++ b/pkg/cbox/preferences/sql/sql.go @@ -0,0 +1,100 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sql + +import ( + "context" + "database/sql" + "fmt" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" + "github.com/mitchellh/mapstructure" +) + +func init() { + registry.Register("sql", New) +} + +type config struct { + DbUsername string `mapstructure:"db_username"` + DbPassword string `mapstructure:"db_password"` + DbHost string `mapstructure:"db_host"` + DbPort int `mapstructure:"db_port"` + DbName string `mapstructure:"db_name"` +} + +type mgr struct { + c *config + db *sql.DB +} + +// New returns an instance of the cbox sql preferences manager. +func New(m map[string]interface{}) (preferences.Manager, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) + if err != nil { + return nil, err + } + + return &mgr{ + c: c, + db: db, + }, nil +} + +func (m *mgr) SetKey(ctx context.Context, key, namespace, value string) error { + user, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return errtypes.UserRequired("preferences: error getting user from ctx") + } + query := `INSERT INTO oc_preferences(userid, appid, configkey, configvalue) values(?, ?, ?, ?) ON DUPLICATE KEY UPDATE configvalue = ?` + params := []interface{}{user.Id.OpaqueId, namespace, key, value, value} + stmt, err := m.db.Prepare(query) + if err != nil { + return err + } + + if _, err = stmt.Exec(params...); err != nil { + return err + } + return nil +} + +func (m *mgr) GetKey(ctx context.Context, key, namespace string) (string, error) { + user, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return "", errtypes.UserRequired("preferences: error getting user from ctx") + } + query := `SELECT configvalue FROM oc_preferences WHERE userid=? AND appid=? AND configkey=?` + var val string + if err := m.db.QueryRow(query, user.Id.OpaqueId, namespace, key).Scan(&val); err != nil { + if err == sql.ErrNoRows { + return "", errtypes.NotFound(namespace + ":" + key) + } + return "", err + } + return val, nil +} diff --git a/pkg/cbox/storage/eoswrapper/eoswrapper.go b/pkg/cbox/storage/eoswrapper/eoswrapper.go index 3c2a3294ca..5cbfc0feed 100644 --- a/pkg/cbox/storage/eoswrapper/eoswrapper.go +++ b/pkg/cbox/storage/eoswrapper/eoswrapper.go @@ -21,15 +21,17 @@ package eoswrapper import ( "bytes" "context" + "io" "strings" "text/template" "github.com/Masterminds/sprig" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/storage" - "github.com/cs3org/reva/v2/pkg/storage/fs/registry" - "github.com/cs3org/reva/v2/pkg/storage/utils/eosfs" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" + "github.com/cs3org/reva/pkg/storage/utils/eosfs" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -67,6 +69,7 @@ func parseConfig(m map[string]interface{}) (*eosfs.Config, string, error) { // allow recycle operations for project spaces if !c.EnableHome && strings.HasPrefix(c.Namespace, eosProjectsNamespace) { c.AllowPathRecycleOperations = true + c.ImpersonateOwnerforRevisions = true } t, ok := m["mount_id_template"].(string) @@ -135,6 +138,30 @@ func (w *wrapper) ListFolder(ctx context.Context, ref *provider.Reference, mdKey return res, nil } +func (w *wrapper) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return nil, err + } + + return w.FS.ListRevisions(ctx, ref) +} + +func (w *wrapper) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return nil, err + } + + return w.FS.DownloadRevision(ctx, ref, revisionKey) +} + +func (w *wrapper) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return err + } + + return w.FS.RestoreRevision(ctx, ref, revisionKey) +} + func (w *wrapper) getMountID(ctx context.Context, r *provider.ResourceInfo) string { if r == nil { return "" @@ -173,3 +200,33 @@ func (w *wrapper) setProjectSharingPermissions(ctx context.Context, r *provider. } return nil } + +func (w *wrapper) userIsProjectAdmin(ctx context.Context, ref *provider.Reference) error { + // Check if this storage provider corresponds to a project spaces instance + if !strings.HasPrefix(w.conf.Namespace, eosProjectsNamespace) { + return nil + } + + res, err := w.FS.GetMD(ctx, ref, nil) + if err != nil { + return err + } + + // Extract project name from the path resembling /c/cernbox or /c/cernbox/minutes/.. + parts := strings.SplitN(res.Path, "/", 4) + if len(parts) != 4 && len(parts) != 3 { + // The request might be for / or /$letter + // Nothing to do in that case + return nil + } + adminGroup := projectSpaceGroupsPrefix + parts[2] + projectSpaceAdminGroupsSuffix + user := ctxpkg.ContextMustGetUser(ctx) + + for _, g := range user.Groups { + if g == adminGroup { + return nil + } + } + + return errtypes.PermissionDenied("eosfs: project spaces revisions can only be accessed by admins") +} diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 47b839f4d1..2703c798db 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -20,7 +20,6 @@ package rest import ( "context" - "errors" "fmt" "net/url" "regexp" @@ -34,6 +33,7 @@ import ( "github.com/cs3org/reva/v2/pkg/user/manager/registry" "github.com/gomodule/redigo/redis" "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" ) func init() { @@ -143,7 +143,7 @@ func (m *manager) getUser(ctx context.Context, url string) (map[string]interface t, _ := userData["type"].(string) userType := getUserType(t, userData["upn"].(string)) - if userType != userpb.UserType_USER_TYPE_APPLICATION && userType != userpb.UserType_USER_TYPE_FEDERATED { + if userType != userpb.UserType_USER_TYPE_APPLICATION { users = append(users, userData) } } @@ -198,8 +198,15 @@ func (m *manager) getInternalUserID(ctx context.Context, uid *userpb.UserId) (st return internalID, nil } -func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]interface{}) *userpb.User { - upn, _ := userData["upn"].(string) +func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]interface{}) (*userpb.User, error) { + id, ok := userData["id"].(string) + if !ok { + return nil, errors.New("parseAndCacheUser: Missing id in userData") + } + upn, ok := userData["upn"].(string) + if !ok { + return nil, errors.New("parseAndCacheUser: Missing upn in userData") + } mail, _ := userData["primaryAccountEmail"].(string) name, _ := userData["displayName"].(string) uidNumber, _ := userData["uid"].(float64) @@ -225,15 +232,14 @@ func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]int log := appctx.GetLogger(ctx) log.Error().Err(err).Msg("rest: error caching user details") } - if err := m.cacheInternalID(userID, userData["id"].(string)); err != nil { + if err := m.cacheInternalID(userID, id); err != nil { log := appctx.GetLogger(ctx) - log.Error().Err(err).Msg("rest: error caching user details") + log.Error().Err(err).Msg("rest: error caching internal ID") } - return u - + return u, nil } -func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { u, err := m.fetchCachedUserDetails(uid) if err != nil { var ( @@ -249,22 +255,27 @@ func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User if err != nil { return nil, err } - u = m.parseAndCacheUser(ctx, userData) + u, err = m.parseAndCacheUser(ctx, userData) + if err != nil { + return nil, err + } } - userGroups, err := m.GetUserGroups(ctx, uid) - if err != nil { - return nil, err + if !skipFetchingGroups { + userGroups, err := m.GetUserGroups(ctx, uid) + if err != nil { + return nil, err + } + u.Groups = userGroups } - u.Groups = userGroups return u, nil } -func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { opaqueID, err := m.fetchCachedParam(claim, value) if err == nil { - return m.GetUser(ctx, &userpb.UserId{OpaqueId: opaqueID}) + return m.GetUser(ctx, &userpb.UserId{OpaqueId: opaqueID}, skipFetchingGroups) } switch claim { @@ -278,7 +289,14 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use return nil, errors.New("rest: invalid field: " + claim) } - userData, err := m.getUserByParam(ctx, claim, value) + var userData map[string]interface{} + if claim == "upn" && strings.HasPrefix(value, "guest:") { + // Lightweight accounts need to be fetched by email, regardless of the demanded claim + userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(value, "guest:")) + } else { + userData, err = m.getUserByParam(ctx, claim, value) + } + if err != nil { // Lightweight accounts need to be fetched by email if strings.HasPrefix(value, "guest:") { @@ -287,19 +305,23 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use } } } - u := m.parseAndCacheUser(ctx, userData) - - userGroups, err := m.GetUserGroups(ctx, u.Id) + u, err := m.parseAndCacheUser(ctx, userData) if err != nil { return nil, err } - u.Groups = userGroups - return u, nil + if !skipFetchingGroups { + userGroups, err := m.GetUserGroups(ctx, u.Id) + if err != nil { + return nil, err + } + u.Groups = userGroups + } + return u, nil } -func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[string]*userpb.User) error { +func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[string]*userpb.User, skipFetchingGroups bool) error { userData, err := m.apiTokenManager.SendAPIGetRequest(ctx, url, false) if err != nil { @@ -312,7 +334,10 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s continue } - upn, _ := usrInfo["upn"].(string) + upn, ok := usrInfo["upn"].(string) + if !ok { + continue + } mail, _ := usrInfo["primaryAccountEmail"].(string) name, _ := usrInfo["displayName"].(string) uidNumber, _ := usrInfo["uid"].(float64) @@ -320,7 +345,7 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s t, _ := usrInfo["type"].(string) userType := getUserType(t, upn) - if userType == userpb.UserType_USER_TYPE_APPLICATION || userType == userpb.UserType_USER_TYPE_FEDERATED { + if userType == userpb.UserType_USER_TYPE_APPLICATION { continue } @@ -329,6 +354,14 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s Idp: m.conf.IDProvider, Type: userType, } + var userGroups []string + if !skipFetchingGroups { + userGroups, err = m.GetUserGroups(ctx, uid) + if err != nil { + return err + } + } + users[uid.OpaqueId] = &userpb.User{ Id: uid, Username: upn, @@ -336,17 +369,18 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s DisplayName: name, UidNumber: int64(uidNumber), GidNumber: int64(gidNumber), + Groups: userGroups, } } return nil } -func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { // Look at namespaces filters. If the query starts with: // "a" => look into primary/secondary/service accounts - // "l" => look into lightweight accounts + // "l" => look into lightweight/federated accounts // none => look into primary parts := strings.SplitN(query, ":", 2) @@ -372,7 +406,7 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, for _, f := range filters { url := fmt.Sprintf("%s/Identity/?filter=%s:contains:%s&field=id&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", m.conf.APIBaseURL, f, url.QueryEscape(query)) - err := m.findUsersByFilter(ctx, url, users) + err := m.findUsersByFilter(ctx, url, users, skipFetchingGroups) if err != nil { return nil, err } @@ -387,7 +421,7 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, case "a": accountsFilters = []userpb.UserType{userpb.UserType_USER_TYPE_PRIMARY, userpb.UserType_USER_TYPE_SECONDARY, userpb.UserType_USER_TYPE_SERVICE} case "l": - accountsFilters = []userpb.UserType{userpb.UserType_USER_TYPE_LIGHTWEIGHT} + accountsFilters = []userpb.UserType{userpb.UserType_USER_TYPE_LIGHTWEIGHT, userpb.UserType_USER_TYPE_FEDERATED} } for _, u := range users { diff --git a/pkg/cbox/utils/conversions.go b/pkg/cbox/utils/conversions.go index 1391df675a..271f8d5cb1 100644 --- a/pkg/cbox/utils/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -199,6 +199,8 @@ func ExtractUserID(u string) *userpb.UserId { t := userpb.UserType_USER_TYPE_PRIMARY if strings.HasPrefix(u, "guest:") { t = userpb.UserType_USER_TYPE_LIGHTWEIGHT + } else if strings.Contains(u, "@") { + t = userpb.UserType_USER_TYPE_FEDERATED } return &userpb.UserId{OpaqueId: u, Type: t} } @@ -255,7 +257,7 @@ func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { } var expires *typespb.Timestamp if s.Expiration != "" { - t, err := time.Parse("2006-01-02 03:04:05", s.Expiration) + t, err := time.Parse("2006-01-02 15:04:05", s.Expiration) if err == nil { expires = &typespb.Timestamp{ Seconds: uint64(t.Unix()), diff --git a/pkg/datatx/datatx.go b/pkg/datatx/datatx.go new file mode 100644 index 0000000000..3b1d98d6b2 --- /dev/null +++ b/pkg/datatx/datatx.go @@ -0,0 +1,38 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package datatx + +import ( + "context" + + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" +) + +// Manager the interface any transfer driver should implement +type Manager interface { + // StartTransfer initiates a transfer job and returns a TxInfo object including a unique transfer id, and error if any. + StartTransfer(ctx context.Context, srcRemote string, srcPath string, srcToken string, destRemote string, destPath string, destToken string) (*datatx.TxInfo, error) + // GetTransferStatus returns a TxInfo object including the current status, and error if any. + GetTransferStatus(ctx context.Context, transferID string) (*datatx.TxInfo, error) + // CancelTransfer cancels the transfer and returns a TxInfo object and error if any. + CancelTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) + // RetryTransfer retries the transfer and returns a TxInfo object and error if any. + // Note that tokens must still be valid. + RetryTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) +} diff --git a/pkg/datatx/manager/loader/loader.go b/pkg/datatx/manager/loader/loader.go new file mode 100644 index 0000000000..28df90ed12 --- /dev/null +++ b/pkg/datatx/manager/loader/loader.go @@ -0,0 +1,25 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package loader + +import ( + // Load datatx drivers. + _ "github.com/cs3org/reva/pkg/datatx/manager/rclone" + // Add your own here +) diff --git a/pkg/datatx/manager/rclone/rclone.go b/pkg/datatx/manager/rclone/rclone.go new file mode 100644 index 0000000000..5e4008cbbf --- /dev/null +++ b/pkg/datatx/manager/rclone/rclone.go @@ -0,0 +1,831 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package rclone + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "path" + "strconv" + "sync" + "time" + + datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + txdriver "github.com/cs3org/reva/pkg/datatx" + registry "github.com/cs3org/reva/pkg/datatx/manager/registry" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/google/uuid" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +func init() { + registry.Register("rclone", New) +} + +func (c *config) init(m map[string]interface{}) { + // set sane defaults + if c.File == "" { + c.File = "/var/tmp/reva/datatx-transfers.json" + } + if c.JobStatusCheckInterval == 0 { + c.JobStatusCheckInterval = 2000 + } + if c.JobTimeout == 0 { + c.JobTimeout = 50000 + } +} + +type config struct { + Endpoint string `mapstructure:"endpoint"` + AuthUser string `mapstructure:"auth_user"` // rclone basicauth user + AuthPass string `mapstructure:"auth_pass"` // rclone basicauth pass + File string `mapstructure:"file"` + JobStatusCheckInterval int `mapstructure:"job_status_check_interval"` + JobTimeout int `mapstructure:"job_timeout"` +} + +type rclone struct { + config *config + client *http.Client + pDriver *pDriver +} + +type rcloneHTTPErrorRes struct { + Error string `json:"error"` + Input map[string]interface{} `json:"input"` + Path string `json:"path"` + Status int `json:"status"` +} + +type transferModel struct { + File string + Transfers map[string]*transfer `json:"transfers"` +} + +// persistency driver +type pDriver struct { + sync.Mutex // concurrent access to the file + model *transferModel +} + +type transfer struct { + TransferID string + JobID int64 + TransferStatus datatx.Status + SrcToken string + SrcRemote string + SrcPath string + DestToken string + DestRemote string + DestPath string + Ctime string +} + +// txEndStatuses final statuses that cannot be changed anymore +var txEndStatuses = map[string]int32{ + "STATUS_INVALID": 0, + "STATUS_DESTINATION_NOT_FOUND": 1, + "STATUS_TRANSFER_COMPLETE": 6, + "STATUS_TRANSFER_FAILED": 7, + "STATUS_TRANSFER_CANCELLED": 8, + "STATUS_TRANSFER_CANCEL_FAILED": 9, + "STATUS_TRANSFER_EXPIRED": 10, +} + +// New returns a new rclone driver +func New(m map[string]interface{}) (txdriver.Manager, error) { + c, err := parseConfig(m) + if err != nil { + return nil, err + } + c.init(m) + + // TODO insecure should be configurable + client := rhttp.GetHTTPClient(rhttp.Insecure(true)) + + // The persistency driver + // Load or create 'db' + model, err := loadOrCreate(c.File) + if err != nil { + err = errors.Wrap(err, "error loading the file containing the transfers") + return nil, err + } + pDriver := &pDriver{ + model: model, + } + + return &rclone{ + config: c, + client: client, + pDriver: pDriver, + }, nil +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} + +func loadOrCreate(file string) (*transferModel, error) { + _, err := os.Stat(file) + if os.IsNotExist(err) { + if err := ioutil.WriteFile(file, []byte("{}"), 0700); err != nil { + err = errors.Wrap(err, "error creating the transfers storage file: "+file) + return nil, err + } + } + + fd, err := os.OpenFile(file, os.O_CREATE, 0644) + if err != nil { + err = errors.Wrap(err, "error opening the transfers storage file: "+file) + return nil, err + } + defer fd.Close() + + data, err := ioutil.ReadAll(fd) + if err != nil { + err = errors.Wrap(err, "error reading the data") + return nil, err + } + + model := &transferModel{} + if err := json.Unmarshal(data, model); err != nil { + err = errors.Wrap(err, "error decoding transfers data to json") + return nil, err + } + + if model.Transfers == nil { + model.Transfers = make(map[string]*transfer) + } + + model.File = file + return model, nil +} + +// saveTransfer saves the transfer. If an error is specified than that error will be returned, possibly wrapped with additional errors. +func (m *transferModel) saveTransfer(e error) error { + data, err := json.Marshal(m) + if err != nil { + e = errors.Wrap(err, "error encoding transfer data to json") + return e + } + + if err := ioutil.WriteFile(m.File, data, 0644); err != nil { + e = errors.Wrap(err, "error writing transfer data to file: "+m.File) + return e + } + + return e +} + +// StartTransfer initiates a transfer job and returns a TxInfo object that includes a unique transfer id. +func (driver *rclone) StartTransfer(ctx context.Context, srcRemote string, srcPath string, srcToken string, destRemote string, destPath string, destToken string) (*datatx.TxInfo, error) { + return driver.startJob(ctx, "", srcRemote, srcPath, srcToken, destRemote, destPath, destToken) +} + +// startJob starts a transfer job. Retries a previous job if transferID is specified. +func (driver *rclone) startJob(ctx context.Context, transferID string, srcRemote string, srcPath string, srcToken string, destRemote string, destPath string, destToken string) (*datatx.TxInfo, error) { + logger := appctx.GetLogger(ctx) + + driver.pDriver.Lock() + defer driver.pDriver.Unlock() + + var txID string + var cTime *typespb.Timestamp + + if transferID == "" { + txID = uuid.New().String() + cTime = &typespb.Timestamp{Seconds: uint64(time.Now().Unix())} + } else { // restart existing transfer if transferID is specified + logger.Debug().Msgf("Restarting transfer (txID: %s)", transferID) + txID = transferID + transfer, err := driver.pDriver.model.getTransfer(txID) + if err != nil { + err = errors.Wrap(err, "rclone: error retrying transfer (transferID: "+txID+")") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: nil, + }, err + } + seconds, _ := strconv.ParseInt(transfer.Ctime, 10, 64) + cTime = &typespb.Timestamp{Seconds: uint64(seconds)} + _, endStatusFound := txEndStatuses[transfer.TransferStatus.String()] + if !endStatusFound { + err := errors.New("rclone: transfer still running, unable to restart") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, err + } + srcToken = transfer.SrcToken + srcRemote = transfer.SrcRemote + srcPath = transfer.SrcPath + destToken = transfer.DestToken + destRemote = transfer.DestRemote + destPath = transfer.DestPath + delete(driver.pDriver.model.Transfers, txID) + } + + transferStatus := datatx.Status_STATUS_TRANSFER_NEW + + transfer := &transfer{ + TransferID: txID, + JobID: int64(-1), + TransferStatus: transferStatus, + SrcToken: srcToken, + SrcRemote: srcRemote, + SrcPath: srcPath, + DestToken: destToken, + DestRemote: destRemote, + DestPath: destPath, + Ctime: fmt.Sprint(cTime.Seconds), // TODO do we need nanos here? + } + + driver.pDriver.model.Transfers[txID] = transfer + + type rcloneAsyncReqJSON struct { + SrcFs string `json:"srcFs"` + // SrcToken string `json:"srcToken"` + DstFs string `json:"dstFs"` + // DstToken string `json:"destToken"` + Async bool `json:"_async"` + } + srcFs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":%v", srcToken, srcRemote, srcPath) + dstFs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":%v", destToken, destRemote, destPath) + rcloneReq := &rcloneAsyncReqJSON{ + SrcFs: srcFs, + DstFs: dstFs, + Async: true, + } + data, err := json.Marshal(rcloneReq) + if err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer: error marshalling rclone req data") + transfer.TransferStatus = datatx.Status_STATUS_INVALID + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + + transferFileMethod := "/sync/copy" + remotePathIsFolder, err := driver.remotePathIsFolder(srcRemote, srcPath, srcToken) + if err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer: error stating src path") + transfer.TransferStatus = datatx.Status_STATUS_INVALID + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + if !remotePathIsFolder { + err = errors.Wrap(err, "rclone: error pulling transfer: path is a file, only folder transfer is implemented") + transfer.TransferStatus = datatx.Status_STATUS_INVALID + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + + u, err := url.Parse(driver.config.Endpoint) + if err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer: error parsing driver endpoint") + transfer.TransferStatus = datatx.Status_STATUS_INVALID + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + u.Path = path.Join(u.Path, transferFileMethod) + requestURL := u.String() + req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data)) + if err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer: error framing post request") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass) + res, err := driver.client.Do(req) + if err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer: error sending post request") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + var errorResData rcloneHTTPErrorRes + if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil { + err = errors.Wrap(err, "rclone driver: error decoding response data") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + e := errors.New("rclone: rclone request responded with error, " + fmt.Sprintf(" status: %v, error: %v", errorResData.Status, errorResData.Error)) + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(e) + } + + type rcloneAsyncResJSON struct { + JobID int64 `json:"jobid"` + } + var resData rcloneAsyncResJSON + if err = json.NewDecoder(res.Body).Decode(&resData); err != nil { + err = errors.Wrap(err, "rclone: error decoding response data") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transfer.TransferStatus, + Ctime: cTime, + }, driver.pDriver.model.saveTransfer(err) + } + + transfer.JobID = resData.JobID + + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + err = errors.Wrap(err, "rclone: error pulling transfer") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: cTime, + }, err + } + + // start separate dedicated process to periodically check the transfer progress + go func() { + // runs for as long as no end state or time out has been reached + startTimeMs := time.Now().Nanosecond() / 1000 + timeout := driver.config.JobTimeout + + driver.pDriver.Lock() + defer driver.pDriver.Unlock() + + for { + transfer, err := driver.pDriver.model.getTransfer(txID) + if err != nil { + transfer.TransferStatus = datatx.Status_STATUS_INVALID + err = driver.pDriver.model.saveTransfer(err) + logger.Error().Err(err).Msgf("rclone driver: unable to retrieve transfer with id: %v", txID) + break + } + + // check for end status first + _, endStatusFound := txEndStatuses[transfer.TransferStatus.String()] + if endStatusFound { + logger.Info().Msgf("rclone driver: transfer endstatus reached: %v", transfer.TransferStatus) + break + } + + // check for possible timeout and if true were done + currentTimeMs := time.Now().Nanosecond() / 1000 + timePastMs := currentTimeMs - startTimeMs + + if timePastMs > timeout { + logger.Info().Msgf("rclone driver: transfer timed out: %vms (timeout = %v)", timePastMs, timeout) + // set status to EXPIRED and save + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_EXPIRED + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + + jobID := transfer.JobID + type rcloneStatusReqJSON struct { + JobID int64 `json:"jobid"` + } + rcloneStatusReq := &rcloneStatusReqJSON{ + JobID: jobID, + } + + data, err := json.Marshal(rcloneStatusReq) + if err != nil { + logger.Error().Err(err).Msgf("rclone driver: marshalling request failed: %v", err) + transfer.TransferStatus = datatx.Status_STATUS_INVALID + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + + transferFileMethod := "/job/status" + + u, err := url.Parse(driver.config.Endpoint) + if err != nil { + logger.Error().Err(err).Msgf("rclone driver: could not parse driver endpoint: %v", err) + transfer.TransferStatus = datatx.Status_STATUS_INVALID + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + u.Path = path.Join(u.Path, transferFileMethod) + requestURL := u.String() + + req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data)) + if err != nil { + logger.Error().Err(err).Msgf("rclone driver: error framing post request: %v", err) + transfer.TransferStatus = datatx.Status_STATUS_INVALID + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass) + res, err := driver.client.Do(req) + if err != nil { + logger.Error().Err(err).Msgf("rclone driver: error sending post request: %v", err) + transfer.TransferStatus = datatx.Status_STATUS_INVALID + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + var errorResData rcloneHTTPErrorRes + if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil { + err = errors.Wrap(err, "rclone driver: error decoding response data") + logger.Error().Err(err).Msgf("rclone driver: error reading response body: %v", err) + } + logger.Error().Err(err).Msgf("rclone driver: rclone request responded with error, status: %v, error: %v", errorResData.Status, errorResData.Error) + transfer.TransferStatus = datatx.Status_STATUS_INVALID + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err) + } + break + } + + type rcloneStatusResJSON struct { + Finished bool `json:"finished"` + Success bool `json:"success"` + ID int64 `json:"id"` + Error string `json:"error"` + Group string `json:"group"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Duration float64 `json:"duration"` + // think we don't need this + // "output": {} // output of the job as would have been returned if called synchronously + } + var resData rcloneStatusResJSON + if err = json.NewDecoder(res.Body).Decode(&resData); err != nil { + logger.Error().Err(err).Msgf("rclone driver: error decoding response data: %v", err) + break + } + + if resData.Error != "" { + logger.Error().Err(err).Msgf("rclone driver: rclone responded with error: %v", resData.Error) + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err) + break + } + break + } + + // transfer complete + if resData.Finished && resData.Success { + logger.Info().Msg("rclone driver: transfer job finished") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_COMPLETE + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err) + break + } + break + } + + // transfer completed unsuccessfully without error + if resData.Finished && !resData.Success { + logger.Info().Msgf("rclone driver: transfer job failed") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err) + break + } + break + } + + // transfer not yet finished: continue + if !resData.Finished { + logger.Info().Msgf("rclone driver: transfer job in progress") + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_IN_PROGRESS + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err) + break + } + } + + <-time.After(time.Millisecond * time.Duration(driver.config.JobStatusCheckInterval)) + } + }() + + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: txID}, + Status: transferStatus, + Ctime: cTime, + }, nil +} + +// GetTransferStatus returns the status of the transfer with the specified job id +func (driver *rclone) GetTransferStatus(ctx context.Context, transferID string) (*datatx.TxInfo, error) { + transfer, err := driver.pDriver.model.getTransfer(transferID) + if err != nil { + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: nil, + }, err + } + cTime, _ := strconv.ParseInt(transfer.Ctime, 10, 64) + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: transfer.TransferStatus, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, nil +} + +// CancelTransfer cancels the transfer with the specified transfer id +func (driver *rclone) CancelTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) { + transfer, err := driver.pDriver.model.getTransfer(transferID) + if err != nil { + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: nil, + }, err + } + cTime, _ := strconv.ParseInt(transfer.Ctime, 10, 64) + _, endStatusFound := txEndStatuses[transfer.TransferStatus.String()] + if endStatusFound { + err := errors.New("rclone driver: transfer already in end state") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + // rcloneStop the rclone job/stop method json request + type rcloneStopRequest struct { + JobID int64 `json:"jobid"` + } + rcloneCancelTransferReq := &rcloneStopRequest{ + JobID: transfer.JobID, + } + + data, err := json.Marshal(rcloneCancelTransferReq) + if err != nil { + err = errors.Wrap(err, "rclone driver: error marshalling rclone req data") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + transferFileMethod := "/job/stop" + + u, err := url.Parse(driver.config.Endpoint) + if err != nil { + err = errors.Wrap(err, "rclone driver: error parsing driver endpoint") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + u.Path = path.Join(u.Path, transferFileMethod) + requestURL := u.String() + + req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data)) + if err != nil { + err = errors.Wrap(err, "rclone driver: error framing post request") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + req.Header.Set("Content-Type", "application/json") + + req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass) + + res, err := driver.client.Do(req) + if err != nil { + err = errors.Wrap(err, "rclone driver: error sending post request") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + var errorResData rcloneHTTPErrorRes + if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil { + err = errors.Wrap(err, "rclone driver: error decoding response data") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + err = errors.Wrap(errors.Errorf("status: %v, error: %v", errorResData.Status, errorResData.Error), "rclone driver: rclone request responded with error") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + type rcloneCancelTransferResJSON struct { + Finished bool `json:"finished"` + Success bool `json:"success"` + ID int64 `json:"id"` + Error string `json:"error"` + Group string `json:"group"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Duration float64 `json:"duration"` + // think we don't need this + // "output": {} // output of the job as would have been returned if called synchronously + } + var resData rcloneCancelTransferResJSON + if err = json.NewDecoder(res.Body).Decode(&resData); err != nil { + err = errors.Wrap(err, "rclone driver: error decoding response data") + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + if resData.Error != "" { + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_TRANSFER_CANCEL_FAILED, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, errors.New(resData.Error) + } + + transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_CANCELLED + if err := driver.pDriver.model.saveTransfer(nil); err != nil { + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_INVALID, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, err + } + + return &datatx.TxInfo{ + Id: &datatx.TxId{OpaqueId: transferID}, + Status: datatx.Status_STATUS_TRANSFER_CANCELLED, + Ctime: &typespb.Timestamp{Seconds: uint64(cTime)}, + }, nil +} + +// RetryTransfer retries the transfer with the specified transfer ID. +// Note that tokens must still be valid. +func (driver *rclone) RetryTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) { + return driver.startJob(ctx, transferID, "", "", "", "", "", "") +} + +// getTransfer returns the transfer with the specified transfer ID +func (m *transferModel) getTransfer(transferID string) (*transfer, error) { + transfer, ok := m.Transfers[transferID] + if !ok { + return nil, errors.New("rclone driver: invalid transfer ID") + } + return transfer, nil +} + +func (driver *rclone) remotePathIsFolder(remote string, remotePath string, remoteToken string) (bool, error) { + type rcloneListReqJSON struct { + Fs string `json:"fs"` + Remote string `json:"remote"` + } + fs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":", remoteToken, remote) + rcloneReq := &rcloneListReqJSON{ + Fs: fs, + Remote: remotePath, + } + data, err := json.Marshal(rcloneReq) + if err != nil { + return false, errors.Wrap(err, "rclone: error marshalling rclone req data") + } + + listMethod := "/operations/list" + + u, err := url.Parse(driver.config.Endpoint) + if err != nil { + return false, errors.Wrap(err, "rclone driver: error parsing driver endpoint") + } + u.Path = path.Join(u.Path, listMethod) + requestURL := u.String() + + req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data)) + if err != nil { + return false, errors.Wrap(err, "rclone driver: error framing post request") + } + req.Header.Set("Content-Type", "application/json") + + req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass) + + res, err := driver.client.Do(req) + if err != nil { + return false, errors.Wrap(err, "rclone driver: error sending post request") + } + + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + var errorResData rcloneHTTPErrorRes + if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil { + return false, errors.Wrap(err, "rclone driver: error decoding response data") + } + return false, errors.Wrap(errors.Errorf("status: %v, error: %v", errorResData.Status, errorResData.Error), "rclone driver: rclone request responded with error") + } + + type item struct { + Path string `json:"Path"` + Name string `json:"Name"` + Size int64 `json:"Size"` + MimeType string `json:"MimeType"` + ModTime string `json:"ModTime"` + IsDir bool `json:"IsDir"` + } + type rcloneListResJSON struct { + List []*item `json:"list"` + } + + var resData rcloneListResJSON + if err = json.NewDecoder(res.Body).Decode(&resData); err != nil { + return false, errors.Wrap(err, "rclone driver: error decoding response data") + } + + // a file will return one single item, the file, with path being the remote path and IsDir will be false + if len(resData.List) == 1 && resData.List[0].Path == remotePath && !resData.List[0].IsDir { + return false, nil + } + + // in all other cases the remote path is a directory + return true, nil +} diff --git a/pkg/datatx/manager/registry/registry.go b/pkg/datatx/manager/registry/registry.go new file mode 100644 index 0000000000..95a4830b3c --- /dev/null +++ b/pkg/datatx/manager/registry/registry.go @@ -0,0 +1,36 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package registry + +import ( + "github.com/cs3org/reva/pkg/datatx" +) + +// NewFunc is the function that datatx implementations +// should register at init time. +type NewFunc func(map[string]interface{}) (datatx.Manager, error) + +// NewFuncs is a map containing all the registered datatx backends. +var NewFuncs = map[string]NewFunc{} + +// Register registers a new datatx backend new function. +// Not safe for concurrent use. Safe for use from package init. +func Register(name string, f NewFunc) { + NewFuncs[name] = f +} diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index 3546c54a0c..a74fce777f 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -33,10 +33,11 @@ import ( "syscall" "time" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/eosclient" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/storage/utils/acl" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/eosclient" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/storage/utils/acl" "github.com/google/uuid" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" @@ -46,6 +47,7 @@ const ( versionPrefix = ".sys.v#." lwShareAttrKey = "reva.lwshare" userACLEvalKey = "eval.useracl" + favoritesKey = "http://owncloud.org/ns/favorite" ) const ( @@ -290,7 +292,7 @@ func (c *Client) executeEOS(ctx context.Context, cmdArgs []string, auth eosclien // AddACL adds an new acl to EOS with the given aclType. func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, pos uint, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, auth, path) + finfo, err := c.getRawFileInfoByPath(ctx, auth, path) if err != nil { return err } @@ -323,20 +325,9 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat } sysACL := a.CitrineSerialize() - args := []string{"acl"} - + args := []string{"acl", "--sys"} if finfo.IsDir { - args = append(args, "--sys", "--recursive") - } else { - args = append(args, "--user") - userACLAttr := &eosclient.Attribute{ - Type: SystemAttr, - Key: userACLEvalKey, - Val: "1", - } - if err = c.SetAttr(ctx, auth, userACLAttr, false, path); err != nil { - return err - } + args = append(args, "--recursive") } // set position of ACLs to add. The default is to append to the end, so no arguments will be added in this case @@ -354,7 +345,7 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat // RemoveACL removes the acl from EOS. func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, auth, path) + finfo, err := c.getRawFileInfoByPath(ctx, auth, path) if err != nil { return err } @@ -387,11 +378,9 @@ func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authori } sysACL := a.CitrineSerialize() - args := []string{"acl"} + args := []string{"acl", "--sys"} if finfo.IsDir { - args = append(args, "--sys", "--recursive") - } else { - args = append(args, "--user") + args = append(args, "--recursive") } args = append(args, sysACL, path) @@ -450,7 +439,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authoriz if err != nil { return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } @@ -463,7 +452,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authoriz info.Inode = inode } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil } // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal @@ -474,12 +463,12 @@ func (c *Client) GetFileInfoByFXID(ctx context.Context, auth eosclient.Authoriza return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil } // GetFileInfoByPath returns the FilInfo at the given path @@ -489,7 +478,7 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authoriza if err != nil { return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } @@ -500,18 +489,38 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authoriza } } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil } -func (c *Client) mergeParentACLsForFiles(ctx context.Context, auth eosclient.Authorization, info *eosclient.FileInfo) *eosclient.FileInfo { +func (c *Client) getRawFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { + args := []string{"file", "info", path, "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) + if err != nil { + return nil, err + } + return c.parseFileInfo(ctx, stdout, false) +} + +func (c *Client) mergeACLsAndAttrsForFiles(ctx context.Context, auth eosclient.Authorization, info *eosclient.FileInfo) *eosclient.FileInfo { // We need to inherit the ACLs for the parent directory as these are not available for files + // And the attributes from the version folders if !info.IsDir { - parentInfo, err := c.GetFileInfoByPath(ctx, auth, path.Dir(info.File)) + parentInfo, err := c.getRawFileInfoByPath(ctx, auth, path.Dir(info.File)) // Even if this call fails, at least return the current file object if err == nil { info.SysACL.Entries = append(info.SysACL.Entries, parentInfo.SysACL.Entries...) } + + // We need to merge attrs set for the version folders, so get those resolved for the current user + versionFolderInfo, err := c.GetFileInfoByPath(ctx, auth, getVersionFolder(info.File)) + if err == nil { + info.SysACL.Entries = append(info.SysACL.Entries, versionFolderInfo.SysACL.Entries...) + for k, v := range versionFolderInfo.Attrs { + info.Attrs[k] = v + } + } } + return info } @@ -520,6 +529,30 @@ func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } + + var info *eosclient.FileInfo + var err error + // We need to set the attrs on the version folder as they are not persisted across writes + // Except for the sys.eval.useracl attr as EOS uses that to determine if it needs to obey + // the user ACLs set on the file + if !(attr.Type == SystemAttr && attr.Key == userACLEvalKey) { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + if !info.IsDir { + path = getVersionFolder(path) + } + } + + // Favorites need to be stored per user so handle these separately + if attr.Type == UserAttr && attr.Key == favoritesKey { + return c.handleFavAttr(ctx, auth, attr, recursive, path, info, true) + } + return c.setEOSAttr(ctx, auth, attr, recursive, path) +} + +func (c *Client) setEOSAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { var args []string if recursive { args = []string{"attr", "-r", "set", serializeAttribute(attr), path} @@ -534,18 +567,65 @@ func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr return nil } +func (c *Client) handleFavAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string, info *eosclient.FileInfo, set bool) error { + var err error + u := ctxpkg.ContextMustGetUser(ctx) + if info == nil { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + } + favStr := info.Attrs[favoritesKey] + favs, err := acl.Parse(favStr, acl.ShortTextForm) + if err != nil { + return err + } + if set { + err = favs.SetEntry(acl.TypeUser, u.Id.OpaqueId, "1") + if err != nil { + return err + } + } else { + favs.DeleteEntry(acl.TypeUser, u.Id.OpaqueId) + } + attr.Val = favs.Serialize() + return c.setEOSAttr(ctx, auth, attr, recursive, path) +} + // UnsetAttr unsets an extended attribute on a path. func (c *Client) UnsetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } + + var info *eosclient.FileInfo + var err error + // We need to set the attrs on the version folder as they are not persisted across writes + // Except for the sys.eval.useracl attr as EOS uses that to determine if it needs to obey + // the user ACLs set on the file + if !(attr.Type == SystemAttr && attr.Key == userACLEvalKey) { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + if !info.IsDir { + path = getVersionFolder(path) + } + } + + // Favorites need to be stored per user so handle these separately + if attr.Type == UserAttr && attr.Key == favoritesKey { + return c.handleFavAttr(ctx, auth, attr, recursive, path, info, false) + } + var args []string if recursive { args = []string{"attr", "-r", "rm", fmt.Sprintf("%s.%s", attrTypeToString(attr.Type), attr.Key), path} } else { args = []string{"attr", "rm", fmt.Sprintf("%s.%s", attrTypeToString(attr.Type), attr.Key), path} } - _, _, err := c.executeEOS(ctx, args, auth) + _, _, err = c.executeEOS(ctx, args, auth) if err != nil { return err } @@ -778,12 +858,12 @@ func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { versionFolder := getVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err := c.getRawFileInfoByPath(ctx, auth, versionFolder) if err != nil { if err = c.CreateDir(ctx, auth, versionFolder); err != nil { return 0, err } - md, err = c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err = c.getRawFileInfoByPath(ctx, auth, versionFolder) if err != nil { return 0, err } @@ -891,7 +971,7 @@ func (c *Client) parseFind(ctx context.Context, auth eosclient.Authorization, di if rl == "" { continue } - fi, err := c.parseFileInfo(rl) + fi, err := c.parseFileInfo(ctx, rl, true) if err != nil { return nil, err } @@ -922,8 +1002,13 @@ func (c *Client) parseFind(ctx context.Context, auth eosclient.Authorization, di versionFolderPath := getVersionFolder(fi.File) if vf, ok := versionFolders[versionFolderPath]; ok { fi.Inode = vf.Inode - } else if err := c.CreateDir(ctx, auth, versionFolderPath); err == nil { - if md, err := c.GetFileInfoByPath(ctx, auth, versionFolderPath); err == nil { + fi.SysACL.Entries = append(fi.SysACL.Entries, vf.SysACL.Entries...) + for k, v := range vf.Attrs { + fi.Attrs[k] = v + } + + } else if err := c.CreateDir(ctx, auth, versionFolderPath); err == nil { // Create the version folder if it doesn't exist + if md, err := c.getRawFileInfoByPath(ctx, auth, versionFolderPath); err == nil { fi.Inode = md.Inode } } @@ -977,7 +1062,7 @@ func (c *Client) parseQuota(path, raw string) (*eosclient.QuotaInfo, error) { } // TODO(labkode): better API to access extended attributes. -func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { +func (c *Client) parseFileInfo(ctx context.Context, raw string, parseFavoriteKey bool) (*eosclient.FileInfo, error) { line := raw[15:] index := strings.Index(line, " file=/") @@ -1019,7 +1104,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { } } } - fi, err := c.mapToFileInfo(kv, attrs) + fi, err := c.mapToFileInfo(ctx, kv, attrs, parseFavoriteKey) if err != nil { return nil, err } @@ -1029,7 +1114,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { // mapToFileInfo converts the dictionary to an usable structure. // The kv has format: // map[sys.forced.space:default files:0 mode:42555 ino:5 sys.forced.blocksize:4k sys.forced.layout:replica uid:0 fid:5 sys.forced.blockchecksum:crc32c sys.recycle:/eos/backup/proc/recycle/ fxid:00000005 pid:1 etag:5:0.000 keylength.file:4 file:/eos treesize:1931593933849913 container:3 gid:0 mtime:1498571294.108614409 ctime:1460121992.294326762 pxid:00000001 sys.forced.checksum:adler sys.forced.nstripes:2] -func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo, error) { +func (c *Client) mapToFileInfo(ctx context.Context, kv, attrs map[string]string, parseFavoriteKey bool) (*eosclient.FileInfo, error) { inode, err := strconv.ParseUint(kv["ino"], 10, 64) if err != nil { return nil, err @@ -1121,22 +1206,6 @@ func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo return nil, err } - // Read user ACLs if sys.eval.useracl is set - if userACLEval, ok := attrs["sys."+userACLEvalKey]; ok && userACLEval == "1" { - if userACL, ok := attrs["user.acl"]; ok { - userAcls, err := acl.Parse(userACL, acl.ShortTextForm) - if err != nil { - return nil, err - } - for _, e := range userAcls.Entries { - err = sysACL.SetEntry(e.Type, e.Qualifier, e.Permissions) - if err != nil { - return nil, err - } - } - } - } - // Read lightweight ACLs recognized by the sys.reva.lwshare attr if lwACLStr, ok := attrs["sys."+lwShareAttrKey]; ok { lwAcls, err := acl.Parse(lwACLStr, acl.ShortTextForm) @@ -1151,6 +1220,11 @@ func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo } } + // Read the favorite attr + if parseFavoriteKey { + parseAndSetFavoriteAttr(ctx, attrs) + } + fi := &eosclient.FileInfo{ File: kv["file"], Inode: inode, @@ -1172,3 +1246,26 @@ func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo return fi, nil } + +func parseAndSetFavoriteAttr(ctx context.Context, attrs map[string]string) { + // Read and correctly set the favorite attr + if user, ok := ctxpkg.ContextGetUser(ctx); ok { + if favAttrStr, ok := attrs[favoritesKey]; ok { + favUsers, err := acl.Parse(favAttrStr, acl.ShortTextForm) + if err != nil { + return + } + for _, u := range favUsers.Entries { + // Check if the current user has favorited this resource + if u.Qualifier == user.Id.OpaqueId { + // Set attr val to 1 + attrs[favoritesKey] = "1" + return + } + } + } + } + + // Delete the favorite attr from the response + delete(attrs, favoritesKey) +} diff --git a/pkg/eosclient/eosgrpc/eosgrpc.go b/pkg/eosclient/eosgrpc/eosgrpc.go index bc49c1af59..5425958f4e 100644 --- a/pkg/eosclient/eosgrpc/eosgrpc.go +++ b/pkg/eosclient/eosgrpc/eosgrpc.go @@ -49,7 +49,6 @@ import ( const ( versionPrefix = ".sys.v#." // lwShareAttrKey = "reva.lwshare" - userACLEvalKey = "eval.useracl" ) const ( @@ -502,22 +501,6 @@ func (c *Client) fixupACLs(ctx context.Context, auth eosclient.Authorization, in } } - // Read user ACLs if sys.eval.useracl is set - if userACLEval, ok := info.Attrs["sys."+userACLEvalKey]; ok && userACLEval == "1" { - if userACL, ok := info.Attrs["user.acl"]; ok { - userAcls, err := acl.Parse(userACL, acl.ShortTextForm) - if err != nil { - return nil - } - for _, e := range userAcls.Entries { - err = info.SysACL.SetEntry(e.Type, e.Qualifier, e.Permissions) - if err != nil { - return nil - } - } - } - } - // We need to inherit the ACLs for the parent directory as these are not available for files if !info.IsDir { parentInfo, err := c.GetFileInfoByPath(ctx, auth, path.Dir(info.File)) diff --git a/pkg/events/example/consumer/consumer.go b/pkg/events/example/consumer/consumer.go index f6cf9f2f77..caeaac2e16 100644 --- a/pkg/events/example/consumer/consumer.go +++ b/pkg/events/example/consumer/consumer.go @@ -23,7 +23,7 @@ import ( "fmt" "log" - "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/pkg/events" ) // Example consumes events from the queue @@ -34,15 +34,8 @@ func Example(c events.Consumer) { // Step 2 - which events does the consumer listen too? evs := []events.Unmarshaller{ + // for example created shares events.ShareCreated{}, - events.ShareUpdated{}, - events.ShareRemoved{}, - events.ReceivedShareUpdated{}, - events.LinkCreated{}, - events.LinkUpdated{}, - events.LinkRemoved{}, - events.LinkAccessed{}, - events.LinkAccessFailed{}, } // Step 3 - create event channel @@ -60,7 +53,7 @@ func Example(c events.Consumer) { case events.ShareCreated: fmt.Printf("%s) Share created: %+v\n", group, v) default: - fmt.Printf("%s) %T: %+v\n", group, v, v) + fmt.Printf("%s) Unregistered event: %+v\n", group, v) } } diff --git a/pkg/events/example/example.go b/pkg/events/example/example.go index f7df3c7fbe..9240fc74db 100644 --- a/pkg/events/example/example.go +++ b/pkg/events/example/example.go @@ -22,19 +22,17 @@ import ( "log" "time" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/events/example/consumer" - "github.com/cs3org/reva/v2/pkg/events/example/publisher" - "github.com/cs3org/reva/v2/pkg/events/server" - "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/asim/go-micro/plugins/events/nats/v4" + "github.com/cs3org/reva/pkg/events" + "github.com/cs3org/reva/pkg/events/example/consumer" + "github.com/cs3org/reva/pkg/events/example/publisher" + "github.com/cs3org/reva/pkg/events/server" ) // Simple example of an event workflow func main() { // start a server - go Server() - - time.Sleep(5 * time.Second) + Server() // obtain a client c := Client() @@ -55,11 +53,7 @@ func main() { // Server generates a nats server func Server() { - err := server.RunNatsServer( - server.ClusterID("test-cluster"), - server.Host("127.0.0.1"), - server.Port(9233), - ) + err := server.RunNatsServer() if err != nil { log.Fatal(err) } @@ -67,10 +61,7 @@ func Server() { // Client builds a nats client func Client() events.Stream { - c, err := server.NewNatsStream( - natsjs.Address("127.0.0.1:9233"), - natsjs.ClusterID("test-cluster"), - ) + c, err := server.NewNatsStream(nats.Address("127.0.0.1:4222"), nats.ClusterID("test-cluster")) if err != nil { log.Fatal(err) } diff --git a/pkg/events/example/publisher/publisher.go b/pkg/events/example/publisher/publisher.go index 59239f4461..ac7ccd686a 100644 --- a/pkg/events/example/publisher/publisher.go +++ b/pkg/events/example/publisher/publisher.go @@ -23,7 +23,7 @@ import ( "log" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/pkg/events" ) // Example publishes events to the queue diff --git a/pkg/events/server/nats.go b/pkg/events/server/nats.go index 9a3763c65c..a205b75cce 100644 --- a/pkg/events/server/nats.go +++ b/pkg/events/server/nats.go @@ -19,50 +19,37 @@ package server import ( - "time" + "fmt" + "github.com/asim/go-micro/plugins/events/nats/v4" "github.com/cenkalti/backoff" - "github.com/cs3org/reva/v2/pkg/logger" "go-micro.dev/v4/events" - "github.com/go-micro/plugins/v4/events/natsjs" - nserver "github.com/nats-io/nats-server/v2/server" + stanServer "github.com/nats-io/nats-streaming-server/server" ) -// RunNatsServer starts the nats server and blocks +// RunNatsServer runs the nats streaming server func RunNatsServer(opts ...Option) error { - options := &nserver.Options{} + natsOpts := stanServer.DefaultNatsServerOptions + stanOpts := stanServer.GetDefaultOptions() for _, o := range opts { - o(options) + o(&natsOpts, stanOpts) } - - server, err := nserver.NewServer(options) - if err != nil { - return err - } - - c := &nserver.JetStreamConfig{} - - err = server.EnableJetStream(c) - if err != nil { - return err - } - - server.Start() - return nil + _, err := stanServer.RunServerWithOpts(stanOpts, &natsOpts) + return err } // NewNatsStream returns a streaming client used by `Consume` and `Publish` methods // retries exponentially to connect to a nats server -func NewNatsStream(opts ...natsjs.Option) (events.Stream, error) { +func NewNatsStream(opts ...nats.Option) (events.Stream, error) { b := backoff.NewExponentialBackOff() var stream events.Stream o := func() error { - n := b.NextBackOff() - s, err := natsjs.NewStream(opts...) - if err != nil && n > time.Second { - logger.New().Error().Err(err).Msgf("can't connect to nats (jetstream) server, retrying in %s", n) + s, err := nats.NewStream(opts...) + if err != nil { + // TODO: should we get the standard logger here? if yes: How? + fmt.Printf("can't connect to nats (stan) server, retrying in %s\n", b.NextBackOff()) } stream = s return err diff --git a/pkg/events/server/options.go b/pkg/events/server/options.go index 835f5c7e99..2cb40fa3ae 100644 --- a/pkg/events/server/options.go +++ b/pkg/events/server/options.go @@ -19,29 +19,37 @@ package server import ( - nserver "github.com/nats-io/nats-server/v2/server" + natsServer "github.com/nats-io/nats-server/v2/server" + stanServer "github.com/nats-io/nats-streaming-server/server" ) // Option configures the nats server -type Option func(*nserver.Options) +type Option func(*natsServer.Options, *stanServer.Options) // Host sets the host URL for the nats server func Host(url string) Option { - return func(o *nserver.Options) { - o.Host = url + return func(no *natsServer.Options, _ *stanServer.Options) { + no.Host = url } } // Port sets the host URL for the nats server func Port(port int) Option { - return func(o *nserver.Options) { - o.Port = port + return func(no *natsServer.Options, _ *stanServer.Options) { + no.Port = port } } -// ClusterID sets the name for the nats cluster -func ClusterID(clusterID string) Option { - return func(o *nserver.Options) { - o.Cluster.Name = clusterID +// NatsOpts allows setting Options from nats package directly +func NatsOpts(opt func(*natsServer.Options)) Option { + return func(no *natsServer.Options, _ *stanServer.Options) { + opt(no) + } +} + +// StanOpts allows setting Options from stan package directly +func StanOpts(opt func(*stanServer.Options)) Option { + return func(_ *natsServer.Options, so *stanServer.Options) { + opt(so) } } diff --git a/pkg/events/types.go b/pkg/events/types.go new file mode 100644 index 0000000000..3ca9dd7f80 --- /dev/null +++ b/pkg/events/types.go @@ -0,0 +1,46 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package events + +import ( + "encoding/json" + + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" +) + +// ShareCreated is emitted when a share is created +type ShareCreated struct { // TODO: Rename to ShareCreatedEvent? + Sharer *user.UserId + // split the protobuf Grantee oneof so we can use stdlib encoding/json + GranteeUserID *user.UserId + GranteeGroupID *group.GroupId + Sharee *provider.Grantee + ItemID *provider.ResourceId + CTime *types.Timestamp +} + +// Unmarshal to fulfill umarshaller interface +func (ShareCreated) Unmarshal(v []byte) (interface{}, error) { + e := ShareCreated{} + err := json.Unmarshal(v, &e) + return e, err +} diff --git a/pkg/group/group.go b/pkg/group/group.go index f27766cbb3..e97d25f3f9 100644 --- a/pkg/group/group.go +++ b/pkg/group/group.go @@ -27,9 +27,9 @@ import ( // Manager is the interface to implement to manipulate groups. type Manager interface { - GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb.Group, error) - GetGroupByClaim(ctx context.Context, claim, value string) (*grouppb.Group, error) - FindGroups(ctx context.Context, query string) ([]*grouppb.Group, error) + GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) + GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) + FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) } diff --git a/pkg/group/manager/json/json.go b/pkg/group/manager/json/json.go index 885cb04c21..ee8b613af7 100644 --- a/pkg/group/manager/json/json.go +++ b/pkg/group/manager/json/json.go @@ -87,19 +87,27 @@ func New(m map[string]interface{}) (group.Manager, error) { }, nil } -func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb.Group, error) { +func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) { for _, g := range m.groups { - if (g.Id.GetOpaqueId() == gid.OpaqueId || g.GroupName == gid.OpaqueId) && (gid.Idp == "" || gid.Idp == g.Id.GetIdp()) { - return g, nil + if g.Id.GetOpaqueId() == gid.OpaqueId || g.GroupName == gid.OpaqueId { + group := *g + if skipFetchingMembers { + group.Members = nil + } + return &group, nil } } return nil, errtypes.NotFound(gid.OpaqueId) } -func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*grouppb.Group, error) { +func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) { for _, g := range m.groups { if groupClaim, err := extractClaim(g, claim); err == nil && value == groupClaim { - return g, nil + group := *g + if skipFetchingMembers { + group.Members = nil + } + return &group, nil } } return nil, errtypes.NotFound(value) @@ -119,11 +127,15 @@ func extractClaim(g *grouppb.Group, claim string) (string, error) { return "", errors.New("json: invalid field") } -func (m *manager) FindGroups(ctx context.Context, query string) ([]*grouppb.Group, error) { +func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) { groups := []*grouppb.Group{} for _, g := range m.groups { if groupContains(g, query) { - groups = append(groups, g) + group := *g + if skipFetchingMembers { + group.Members = nil + } + groups = append(groups, &group) } } return groups, nil diff --git a/pkg/group/manager/json/json_test.go b/pkg/group/manager/json/json_test.go index c14ae04bf1..1cea8a3ab7 100644 --- a/pkg/group/manager/json/json_test.go +++ b/pkg/group/manager/json/json_test.go @@ -102,30 +102,43 @@ func TestUserManager(t *testing.T) { DisplayName: "Sailing Lovers", Members: members, } + groupWithoutMembers := &grouppb.Group{ + Id: gid, + GroupName: "sailing-lovers", + Mail: "sailing-lovers@example.org", + GidNumber: 1234, + DisplayName: "Sailing Lovers", + } groupFake := &grouppb.GroupId{OpaqueId: "fake-group"} // positive test GetGroup - resGroup, _ := manager.GetGroup(ctx, gid) + resGroup, _ := manager.GetGroup(ctx, gid, false) if !reflect.DeepEqual(resGroup, group) { t.Fatalf("group differs: expected=%v got=%v", group, resGroup) } + // positive test GetGroup without members + resGroupWithoutMembers, _ := manager.GetGroup(ctx, gid, true) + if !reflect.DeepEqual(resGroupWithoutMembers, groupWithoutMembers) { + t.Fatalf("group differs: expected=%v got=%v", groupWithoutMembers, resGroupWithoutMembers) + } + // negative test GetGroup expectedErr := errtypes.NotFound(groupFake.OpaqueId) - _, err = manager.GetGroup(ctx, groupFake) + _, err = manager.GetGroup(ctx, groupFake, false) if !reflect.DeepEqual(err, expectedErr) { t.Fatalf("group not found error differ: expected='%v' got='%v'", expectedErr, err) } // positive test GetGroupByClaim by mail - resGroupByEmail, _ := manager.GetGroupByClaim(ctx, "mail", "sailing-lovers@example.org") + resGroupByEmail, _ := manager.GetGroupByClaim(ctx, "mail", "sailing-lovers@example.org", false) if !reflect.DeepEqual(resGroupByEmail, group) { t.Fatalf("group differs: expected=%v got=%v", group, resGroupByEmail) } // negative test GetGroupByClaim by mail expectedErr = errtypes.NotFound("abc@example.com") - _, err = manager.GetGroupByClaim(ctx, "mail", "abc@example.com") + _, err = manager.GetGroupByClaim(ctx, "mail", "abc@example.com", false) if !reflect.DeepEqual(err, expectedErr) { t.Fatalf("group not found error differs: expected='%v' got='%v'", expectedErr, err) } @@ -149,7 +162,7 @@ func TestUserManager(t *testing.T) { } // test FindGroups - resFind, _ := manager.FindGroups(ctx, "sail") + resFind, _ := manager.FindGroups(ctx, "sail", false) if len(resFind) != 1 { t.Fatalf("too many groups found: expected=%d got=%d", 1, len(resFind)) } diff --git a/pkg/group/manager/ldap/ldap.go b/pkg/group/manager/ldap/ldap.go index 617bff21ed..3b7a645b7c 100644 --- a/pkg/group/manager/ldap/ldap.go +++ b/pkg/group/manager/ldap/ldap.go @@ -98,8 +98,7 @@ func (m *manager) Configure(ml map[string]interface{}) error { return nil } -// GetGroup implements the group.Manager interface. Looks up a group by Id and return the group -func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb.Group, error) { +func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) { log := appctx.GetLogger(ctx) if gid.Idp != "" && gid.Idp != m.c.Idp { return nil, errtypes.NotFound("idp mismatch") @@ -117,14 +116,18 @@ func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb. return nil, err } - members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry) - if err != nil { - return nil, err + var members []*userpb.UserId + if !skipFetchingMembers { + members, err = m.GetMembers(ctx, id) + if err != nil { + return nil, err + } } - memberIDs := make([]*userpb.UserId, 0, len(members)) - for _, member := range members { - userid, err := m.ldapEntryToUserID(member) + gidNumber := m.c.Nobody + gidValue := sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.GIDNumber) + if gidValue != "" { + gidNumber, err = strconv.ParseInt(gidValue, 10, 64) if err != nil { log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid") continue @@ -137,9 +140,21 @@ func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId) (*grouppb. return g, nil } -// GetGroupByClaim implements the group.Manager interface. Looks up a group by -// claim ('group_name', 'group_id', 'display_name') and returns the group. -func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*grouppb.Group, error) { +func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) { + // TODO align supported claims with rest driver and the others, maybe refactor into common mapping + switch claim { + case "mail": + claim = m.c.Schema.Mail + case "gid_number": + claim = m.c.Schema.GIDNumber + case "group_name": + claim = m.c.Schema.CN + case "groupid": + claim = m.c.Schema.GID + default: + return nil, errors.New("ldap: invalid field " + claim) + } + log := appctx.GetLogger(ctx) groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByAttribute(log, m.ldapClient, claim, value) if err != nil { @@ -149,12 +164,20 @@ func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*gr log.Debug().Interface("entry", groupEntry).Msg("entries") - g, err := m.ldapEntryToGroup(groupEntry) - if err != nil { - return nil, err + id := &grouppb.GroupId{ + Idp: m.c.Idp, + OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.GID), } - members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry) + var members []*userpb.UserId + if !skipFetchingMembers { + members, err = m.GetMembers(ctx, id) + if err != nil { + return nil, err + } + } + + gidNumber, err := strconv.ParseInt(sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.GIDNumber), 10, 64) if err != nil { return nil, err } @@ -174,25 +197,56 @@ func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*gr return g, nil } -// FindGroups implements the group.Manager interface. Searches for groups using -// a prefix-substring search on the group attributes ('group_name', -// 'display_name', 'group_id') and returns the groups. FindGroups does NOT expand the -// members of the Groups. -func (m *manager) FindGroups(ctx context.Context, query string) ([]*grouppb.Group, error) { - log := appctx.GetLogger(ctx) - entries, err := m.c.LDAPIdentity.GetLDAPGroups(log, m.ldapClient, query) +func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) { + l, err := utils.GetLDAPConnection(&m.c.LDAPConn) + if err != nil { + return nil, err + } + defer l.Close() + + // Search for the given clientID + searchRequest := ldap.NewSearchRequest( + m.c.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + m.getFindFilter(query), + []string{m.c.Schema.DN, m.c.Schema.GID, m.c.Schema.CN, m.c.Schema.Mail, m.c.Schema.DisplayName, m.c.Schema.GIDNumber}, + nil, + ) + + sr, err := l.Search(searchRequest) if err != nil { return nil, err } groups := make([]*grouppb.Group, 0, len(entries)) - for _, entry := range entries { - g, err := m.ldapEntryToGroup(entry) + for _, entry := range sr.Entries { + id := &grouppb.GroupId{ + Idp: m.c.Idp, + OpaqueId: entry.GetEqualFoldAttributeValue(m.c.Schema.GID), + } + + var members []*userpb.UserId + if !skipFetchingMembers { + members, err = m.GetMembers(ctx, id) + if err != nil { + return nil, err + } + } + + gidNumber, err := strconv.ParseInt(entry.GetEqualFoldAttributeValue(m.c.Schema.GIDNumber), 10, 64) if err != nil { return nil, err } + g := &grouppb.Group{ + Id: id, + GroupName: entry.GetEqualFoldAttributeValue(m.c.Schema.CN), + Members: members, + Mail: entry.GetEqualFoldAttributeValue(m.c.Schema.Mail), + DisplayName: entry.GetEqualFoldAttributeValue(m.c.Schema.DisplayName), + GidNumber: gidNumber, + } groups = append(groups, g) } diff --git a/pkg/mentix/config/config.go b/pkg/mentix/config/config.go index 4bf5eeb40d..6ea8abb156 100644 --- a/pkg/mentix/config/config.go +++ b/pkg/mentix/config/config.go @@ -28,10 +28,6 @@ type Configuration struct { Scope string `mapstructure:"scope"` APIKey string `mapstructure:"apikey"` } `mapstructure:"gocdb"` - - LocalFile struct { - File string `mapstructure:"file"` - } `mapstructure:"localfile"` } `mapstructure:"connectors"` UpdateInterval string `mapstructure:"update_interval"` @@ -40,15 +36,6 @@ type Configuration struct { CriticalTypes []string `mapstructure:"critical_types"` } `mapstructure:"services"` - Importers struct { - SiteRegistration struct { - Endpoint string `mapstructure:"endpoint"` - EnabledConnectors []string `mapstructure:"enabled_connectors"` - IsProtected bool `mapstructure:"is_protected"` - IgnoreScienceMeshSites bool `mapstructure:"ignore_sm_sites"` - } `mapstructure:"sitereg"` - } `mapstructure:"importers"` - Exporters struct { WebAPI struct { Endpoint string `mapstructure:"endpoint"` @@ -70,9 +57,8 @@ type Configuration struct { } `mapstructure:"siteloc"` PrometheusSD struct { - MetricsOutputFile string `mapstructure:"metrics_output_file"` - BlackboxOutputFile string `mapstructure:"blackbox_output_file"` - EnabledConnectors []string `mapstructure:"enabled_connectors"` + OutputPath string `mapstructure:"output_path"` + EnabledConnectors []string `mapstructure:"enabled_connectors"` } `mapstructure:"promsd"` Metrics struct { diff --git a/pkg/mentix/config/ids.go b/pkg/mentix/config/ids.go index 348b22b45e..176bf2e314 100644 --- a/pkg/mentix/config/ids.go +++ b/pkg/mentix/config/ids.go @@ -21,13 +21,6 @@ package config const ( // ConnectorIDGOCDB is the connector identifier for GOCDB. ConnectorIDGOCDB = "gocdb" - // ConnectorIDLocalFile is the connector identifier for local files. - ConnectorIDLocalFile = "localfile" -) - -const ( - // ImporterIDSiteRegistration is the identifier for the external site registration importer. - ImporterIDSiteRegistration = "sitereg" ) const ( diff --git a/pkg/mentix/connectors/gocdb.go b/pkg/mentix/connectors/gocdb.go index 980aa9bc9e..35daf3ea9f 100755 --- a/pkg/mentix/connectors/gocdb.go +++ b/pkg/mentix/connectors/gocdb.go @@ -135,16 +135,13 @@ func (connector *GOCDBConnector) querySites(meshData *meshdata.MeshData) error { for _, site := range sites.Sites { properties := connector.extensionsToMap(&site.Extensions) - siteID := meshdata.GetPropertyValue(properties, meshdata.PropertySiteID, "") - if len(siteID) == 0 { - return fmt.Errorf("site ID missing for site '%v'", site.ShortName) - } + // The site ID can be set through a property; by default, the site short name will be used + siteID := meshdata.GetPropertyValue(properties, meshdata.PropertySiteID, site.ShortName) // See if an organization has been defined using properties; otherwise, use the official name organization := meshdata.GetPropertyValue(properties, meshdata.PropertyOrganization, site.OfficialName) meshsite := &meshdata.Site{ - Type: meshdata.SiteTypeScienceMesh, // All sites stored in the GOCDB are part of the mesh ID: siteID, Name: site.ShortName, FullName: site.OfficialName, @@ -295,6 +292,9 @@ func (connector *GOCDBConnector) getServiceURL(service *gocdb.Service, endpoint svcURL.Path = endpoint.URL } else { svcURL.Path = path.Join(svcURL.Path, endpoint.URL) + if strings.HasSuffix(endpoint.URL, "/") { // Restore trailing slash if necessary + svcURL.Path += "/" + } } } } diff --git a/pkg/mentix/exchangers/exchanger.go b/pkg/mentix/exchangers/exchanger.go index 9d45eebf82..f886c15a6d 100644 --- a/pkg/mentix/exchangers/exchanger.go +++ b/pkg/mentix/exchangers/exchanger.go @@ -55,8 +55,7 @@ type BaseExchanger struct { enabledConnectors []string - meshData *meshdata.MeshData - allowUnauthorizedSites bool + meshData *meshdata.MeshData locker sync.RWMutex } @@ -125,22 +124,11 @@ func (exchanger *BaseExchanger) storeMeshDataSet(meshDataSet meshdata.Map) error return nil } -func (exchanger *BaseExchanger) cloneMeshData(clean bool) *meshdata.MeshData { +func (exchanger *BaseExchanger) cloneMeshData() *meshdata.MeshData { exchanger.locker.RLock() meshDataClone := exchanger.meshData.Clone() exchanger.locker.RUnlock() - if clean && !exchanger.allowUnauthorizedSites { - cleanedSites := make([]*meshdata.Site, 0, len(meshDataClone.Sites)) - for _, site := range meshDataClone.Sites { - // Only keep authorized sites - if site.IsAuthorized { - cleanedSites = append(cleanedSites, site) - } - } - meshDataClone.Sites = cleanedSites - } - return meshDataClone } @@ -167,7 +155,7 @@ func (exchanger *BaseExchanger) SetEnabledConnectors(connectors []string) { // MeshData returns the stored mesh data. The returned data is cloned to prevent accidental data changes. // Unauthorized sites are also removed if this exchanger doesn't allow them. func (exchanger *BaseExchanger) MeshData() *meshdata.MeshData { - return exchanger.cloneMeshData(true) + return exchanger.cloneMeshData() } func (exchanger *BaseExchanger) setMeshData(meshData *meshdata.MeshData) { @@ -177,11 +165,6 @@ func (exchanger *BaseExchanger) setMeshData(meshData *meshdata.MeshData) { exchanger.meshData = meshData } -// SetAllowUnauthorizedSites sets whether this exchanger allows the exchange of unauthorized sites. -func (exchanger *BaseExchanger) SetAllowUnauthorizedSites(allow bool) { - exchanger.allowUnauthorizedSites = allow -} - // Locker returns the locking object. func (exchanger *BaseExchanger) Locker() *sync.RWMutex { return &exchanger.locker diff --git a/pkg/mentix/exchangers/exporters/metrics/metrics.go b/pkg/mentix/exchangers/exporters/metrics/metrics.go index 2f424f2d43..7090d9f6ed 100644 --- a/pkg/mentix/exchangers/exporters/metrics/metrics.go +++ b/pkg/mentix/exchangers/exporters/metrics/metrics.go @@ -41,7 +41,6 @@ type Metrics struct { const ( keySiteID = "site_id" keySiteName = "site" - keySiteType = "site_type" keyServiceType = "service_type" ) @@ -70,7 +69,7 @@ func (m *Metrics) registerMetrics() error { Name: m.isScheduledStats.Name(), Description: m.isScheduledStats.Description(), Measure: m.isScheduledStats, - TagKeys: []tag.Key{tag.MustNewKey(keySiteID), tag.MustNewKey(keySiteName), tag.MustNewKey(keySiteType), tag.MustNewKey(keyServiceType)}, + TagKeys: []tag.Key{tag.MustNewKey(keySiteID), tag.MustNewKey(keySiteName), tag.MustNewKey(keyServiceType)}, Aggregation: view.LastValue(), } @@ -96,7 +95,6 @@ func (m *Metrics) exportSiteMetrics(site *meshdata.Site) error { mutators := make([]tag.Mutator, 0) mutators = append(mutators, tag.Insert(tag.MustNewKey(keySiteID), site.ID)) mutators = append(mutators, tag.Insert(tag.MustNewKey(keySiteName), site.Name)) - mutators = append(mutators, tag.Insert(tag.MustNewKey(keySiteType), meshdata.GetSiteTypeName(site.Type))) mutators = append(mutators, tag.Insert(tag.MustNewKey(keyServiceType), "SCIENCEMESH_HCHECK")) // Create a new context to serve the metrics diff --git a/pkg/mentix/exchangers/exporters/promsd.go b/pkg/mentix/exchangers/exporters/promsd.go index 96e0e59b2d..5ae61a8236 100755 --- a/pkg/mentix/exchangers/exporters/promsd.go +++ b/pkg/mentix/exchangers/exporters/promsd.go @@ -24,7 +24,9 @@ import ( "io/ioutil" "net/url" "os" + "path" "path/filepath" + "strings" "github.com/cs3org/reva/v2/pkg/mentix/utils" "github.com/rs/zerolog" @@ -50,10 +52,10 @@ type PrometheusSDExporter struct { const ( labelSiteName = "__meta_mentix_site" - labelSiteType = "__meta_mentix_site_type" labelSiteID = "__meta_mentix_site_id" labelSiteCountry = "__meta_mentix_site_country" labelType = "__meta_mentix_type" + labelURL = "__meta_mentix_url" labelScheme = "__meta_mentix_scheme" labelHost = "__meta_mentix_host" labelPort = "__meta_mentix_port" @@ -74,10 +76,10 @@ func getScrapeTargetLabels(site *meshdata.Site, service *meshdata.Service, endpo endpointURL, _ := url.Parse(endpoint.URL) labels := map[string]string{ labelSiteName: site.Name, - labelSiteType: meshdata.GetSiteTypeName(site.Type), labelSiteID: site.ID, labelSiteCountry: site.CountryCode, labelType: endpoint.Type.Name, + labelURL: endpoint.URL, labelScheme: endpointURL.Scheme, labelHost: endpointURL.Hostname(), labelPort: endpointURL.Port(), @@ -110,12 +112,13 @@ func (exporter *PrometheusSDExporter) registerScrapeCreators(conf *config.Config } // Register all scrape creators - if err := registerCreator("metrics", conf.Exporters.PrometheusSD.MetricsOutputFile, createGenericScrapeConfig, []string{meshdata.EndpointMetrics}); err != nil { - return fmt.Errorf("unable to register the 'metrics' scrape config creator: %v", err) - } + for _, endpoint := range meshdata.GetServiceEndpoints() { + epName := strings.ToLower(endpoint) + filename := path.Join(conf.Exporters.PrometheusSD.OutputPath, "svc_"+epName+".json") - if err := registerCreator("blackbox", conf.Exporters.PrometheusSD.BlackboxOutputFile, createGenericScrapeConfig, []string{meshdata.EndpointGateway}); err != nil { - return fmt.Errorf("unable to register the 'blackbox' scrape config creator: %v", err) + if err := registerCreator(epName, filename, createGenericScrapeConfig, []string{endpoint}); err != nil { + return fmt.Errorf("unable to register the '%v' scrape config creator: %v", epName, err) + } } return nil @@ -199,6 +202,10 @@ func (exporter *PrometheusSDExporter) createScrapeConfigs(creatorCallback promet } } + if scrapes == nil { + scrapes = []*prometheus.ScrapeConfig{} + } + return scrapes } diff --git a/pkg/mentix/exchangers/exporters/webapi.go b/pkg/mentix/exchangers/exporters/webapi.go index 00fbf012f6..b72e0e2565 100755 --- a/pkg/mentix/exchangers/exporters/webapi.go +++ b/pkg/mentix/exchangers/exporters/webapi.go @@ -39,7 +39,6 @@ func (exporter *WebAPIExporter) Activate(conf *config.Configuration, log *zerolo // Store WebAPI specifics exporter.SetEndpoint(conf.Exporters.WebAPI.Endpoint, conf.Exporters.WebAPI.IsProtected) exporter.SetEnabledConnectors(conf.Exporters.WebAPI.EnabledConnectors) - exporter.SetAllowUnauthorizedSites(true) exporter.RegisterActionHandler("", webapi.HandleDefaultQuery) diff --git a/pkg/mentix/exchangers/importers/importers.go b/pkg/mentix/exchangers/importers/importers.go index e9ea3e9277..91a7578c39 100644 --- a/pkg/mentix/exchangers/importers/importers.go +++ b/pkg/mentix/exchangers/importers/importers.go @@ -85,6 +85,9 @@ func AvailableImporters(conf *config.Configuration) (*Collection, error) { return &Collection{Importers: importers}, nil } +// TODO: Uncomment once an importer is actually implemented +/* func registerImporter(importer Importer) { registeredImporters.Register(importer) } +*/ diff --git a/pkg/mentix/mentix.go b/pkg/mentix/mentix.go index 79752406c3..5eb5dd9911 100644 --- a/pkg/mentix/mentix.go +++ b/pkg/mentix/mentix.go @@ -52,7 +52,7 @@ type Mentix struct { } const ( - runLoopSleeptime = time.Millisecond * 500 + runLoopSleeptime = time.Millisecond * 1000 ) func (mntx *Mentix) initialize(conf *config.Configuration, log *zerolog.Logger) error { diff --git a/pkg/mentix/meshdata/endpoints.go b/pkg/mentix/meshdata/endpoints.go index 02228b2040..3f259489c5 100644 --- a/pkg/mentix/meshdata/endpoints.go +++ b/pkg/mentix/meshdata/endpoints.go @@ -19,6 +19,9 @@ package meshdata const ( + // EndpointRevad identifies the main Reva Daemon endpoint + EndpointRevad = "REVAD" + // EndpointGateway identifies the Gateway endpoint EndpointGateway = "GATEWAY" // EndpointMetrics identifies the Metrics endpoint @@ -27,4 +30,19 @@ const ( EndpointWebdav = "WEBDAV" // EndpointOCM identifies the OCM endpoint EndpointOCM = "OCM" + // EndpointMeshDir identifies the Mesh Directory endpoint + EndpointMeshDir = "MESHDIR" ) + +// GetServiceEndpoints returns an array of all service endpoint identifiers. +func GetServiceEndpoints() []string { + return []string{ + EndpointRevad, + + EndpointGateway, + EndpointMetrics, + EndpointWebdav, + EndpointOCM, + EndpointMeshDir, + } +} diff --git a/pkg/mentix/meshdata/site.go b/pkg/mentix/meshdata/site.go index bcc0058b2e..edf45d2ae8 100644 --- a/pkg/mentix/meshdata/site.go +++ b/pkg/mentix/meshdata/site.go @@ -23,26 +23,12 @@ import ( "net/url" "strings" - "github.com/cs3org/reva/v2/pkg/mentix/accservice" - "github.com/cs3org/reva/v2/pkg/mentix/utils/network" + "github.com/cs3org/reva/pkg/mentix/utils/network" ) -const ( - // SiteTypeScienceMesh flags a site as being part of the mesh. - SiteTypeScienceMesh SiteType = iota - // SiteTypeCommunity flags a site as being a community site. - SiteTypeCommunity -) - -// SiteType holds the type of a site. -type SiteType int - // Site represents a single site managed by Mentix. type Site struct { // Internal settings - Type SiteType `json:"-"` - IsAuthorized bool `json:"-"` - ID string Name string FullName string @@ -120,8 +106,6 @@ func (site *Site) Verify() error { // InferMissingData infers missing data from other data where possible. func (site *Site) InferMissingData() { // Infer missing data - site.IsAuthorized = site.getAuthorizationStatus() - if site.Homepage == "" { site.Homepage = fmt.Sprintf("http://www.%v", site.Domain) } else if site.Domain == "" { @@ -135,34 +119,3 @@ func (site *Site) InferMissingData() { service.InferMissingData() } } - -func (site *Site) getAuthorizationStatus() bool { - // ScienceMesh sites are always authorized - if site.Type == SiteTypeScienceMesh { - return true - } - - // Use the accounts service to find out whether the site is authorized - resp, err := accservice.Query("is-authorized", network.URLParams{"by": "siteid", "value": site.ID}) - if err == nil && resp.Success { - if authorized, ok := resp.Data.(bool); ok { - return authorized - } - } - - return false -} - -// GetSiteTypeName returns the readable name of the given site type. -func GetSiteTypeName(siteType SiteType) string { - switch siteType { - case SiteTypeScienceMesh: - return "sciencemesh" - - case SiteTypeCommunity: - return "community" - - default: - return "unknown" - } -} diff --git a/pkg/ocm/invite/manager/json/json.go b/pkg/ocm/invite/manager/json/json.go index 5f1136e5c0..037a85adf9 100644 --- a/pkg/ocm/invite/manager/json/json.go +++ b/pkg/ocm/invite/manager/json/json.go @@ -202,13 +202,6 @@ func (m *manager) ForwardInvite(ctx context.Context, invite *invitepb.InviteToke contextUser := ctxpkg.ContextMustGetUser(ctx) recipientProvider := contextUser.GetId().GetIdp() - // recipientProvider should be a URL, see https://github.com/cs3org/OCM-API/pull/41/files#diff-9cfca4a1b73e1e28e30fb9b0b984aad6d4caaf0819c61ed40ad338600531f745R569 - // And going forward, UserId Idp will also include the https:// prefix, see https://github.com/cs3org/cs3apis/pull/159 - // But historically, reva used UserId Idps that were domains instead of full URLs, so we need to support that case too, see - // https://github.com/cs3org/reva/issues/2288 - if !(strings.Contains(recipientProvider, "://")) { - recipientProvider = "https://" + recipientProvider - } requestBody := url.Values{ "token": {invite.GetToken()}, "userID": {contextUser.GetId().GetOpaqueId()}, diff --git a/pkg/ocm/share/manager/json/json.go b/pkg/ocm/share/manager/json/json.go index 7d00c3a4f9..6f870c9ac7 100644 --- a/pkg/ocm/share/manager/json/json.go +++ b/pkg/ocm/share/manager/json/json.go @@ -32,13 +32,13 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/ocm/share" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/ocm/share" - "github.com/cs3org/reva/v2/pkg/ocm/share/manager/registry" - "github.com/cs3org/reva/v2/pkg/ocm/share/sender" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/ocm/share/manager/registry" + "github.com/cs3org/reva/pkg/ocm/share/sender" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -254,28 +254,17 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGr } if isOwnersMeshProvider { - // token, ok := ctxpkg.ContextGetToken(ctx) - // if !ok { - // return nil, errors.New("Could not get token from context") - // } - var protocol map[string]interface{} + protocol := map[string]interface{}{ + "name": "webdav", + "options": map[string]string{ + "permissions": pm, + "token": ctxpkg.ContextMustGetToken(ctx), + }, + } if st == ocm.Share_SHARE_TYPE_TRANSFER { - protocol = map[string]interface{}{ - "name": "datatx", - "options": map[string]string{ - "permissions": pm, - "token": token, - }, - } - } else { - protocol = map[string]interface{}{ - "name": "webdav", - "options": map[string]string{ - "permissions": pm, - "token": token, - }, - } + protocol["name"] = "datatx" } + requestBodyMap := map[string]interface{}{ "shareWith": g.Grantee.GetUserId().OpaqueId, "name": name, diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud.go b/pkg/ocm/share/manager/nextcloud/nextcloud.go index d186b2e022..57ce41d7be 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud.go @@ -29,19 +29,19 @@ import ( "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/utils" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/utils" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/ocm/share" - "github.com/cs3org/reva/v2/pkg/ocm/share/manager/registry" - "github.com/cs3org/reva/v2/pkg/ocm/share/sender" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/ocm/share" + "github.com/cs3org/reva/pkg/ocm/share/manager/registry" + "github.com/cs3org/reva/pkg/ocm/share/sender" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/genproto/protobuf/field_mask" diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go index 79361be14d..b6565ee7fb 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go @@ -33,8 +33,8 @@ import ( "github.com/cs3org/reva/v2/pkg/auth/scope" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/ocm/share/manager/nextcloud" - jwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/ocm/share/manager/nextcloud" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/pkg/ocm/share/sender/sender.go b/pkg/ocm/share/sender/sender.go index a978ab8e76..5cee734bad 100644 --- a/pkg/ocm/share/sender/sender.go +++ b/pkg/ocm/share/sender/sender.go @@ -29,7 +29,7 @@ import ( "time" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/rhttp" + "github.com/cs3org/reva/pkg/rhttp" "github.com/pkg/errors" ) diff --git a/pkg/permission/manager/demo/demo.go b/pkg/permission/manager/demo/demo.go index 42da025314..7bb6c10779 100644 --- a/pkg/permission/manager/demo/demo.go +++ b/pkg/permission/manager/demo/demo.go @@ -20,8 +20,8 @@ package demo import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/permission" - "github.com/cs3org/reva/v2/pkg/permission/manager/registry" + "github.com/cs3org/reva/pkg/permission" + "github.com/cs3org/reva/pkg/permission/manager/registry" ) func init() { @@ -36,18 +36,8 @@ func New(c map[string]interface{}) (permission.Manager, error) { type manager struct { } -func (m manager) CheckPermission(perm string, subject string, ref *provider.Reference) bool { - switch perm { - case permission.CreateSpace: - // TODO Users can only create their own personal space - // TODO guest accounts cannot create spaces - return true - case permission.ListAllSpaces: - // TODO introduce an admin role to allow listing all spaces - return false - default: - // We can currently return false all the time. - // Once we beginn testing roles we need to somehow check the roles of the users here - return false - } +func (m manager) CheckPermission(permission string, subject string, ref *provider.Reference) bool { + // We can currently return true all the time. + // Once we beginn testing roles we need to somehow check the roles of the users here + return true } diff --git a/pkg/permission/manager/loader/loader.go b/pkg/permission/manager/loader/loader.go index 847e0a6e7a..5f0bbc5774 100644 --- a/pkg/permission/manager/loader/loader.go +++ b/pkg/permission/manager/loader/loader.go @@ -20,6 +20,6 @@ package loader import ( // Load permission manager drivers - _ "github.com/cs3org/reva/v2/pkg/permission/manager/demo" + _ "github.com/cs3org/reva/pkg/permission/manager/demo" // Add your own here ) diff --git a/pkg/permission/manager/registry/registry.go b/pkg/permission/manager/registry/registry.go index 0ae624e1cb..26f55bebad 100644 --- a/pkg/permission/manager/registry/registry.go +++ b/pkg/permission/manager/registry/registry.go @@ -18,7 +18,7 @@ package registry -import "github.com/cs3org/reva/v2/pkg/permission" +import "github.com/cs3org/reva/pkg/permission" // NewFunc is the function that permission managers // should register at init time. diff --git a/pkg/permission/permission.go b/pkg/permission/permission.go index 6aaa808845..e5e5c76a52 100644 --- a/pkg/permission/permission.go +++ b/pkg/permission/permission.go @@ -22,13 +22,6 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ) -const ( - // ListAllSpaces is the hardcoded name for the list all spaces permission - ListAllSpaces string = "list-all-spaces" - // CreateSpace is the hardcoded name for the create space permission - CreateSpace string = "create-space" -) - // Manager defines the interface for the permission service driver type Manager interface { CheckPermission(permission string, subject string, ref *provider.Reference) bool diff --git a/pkg/preferences/loader/loader.go b/pkg/preferences/loader/loader.go new file mode 100644 index 0000000000..a7b4d2c46c --- /dev/null +++ b/pkg/preferences/loader/loader.go @@ -0,0 +1,25 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package loader + +import ( + // Load preferences drivers. + _ "github.com/cs3org/reva/pkg/preferences/memory" + // Add your own here +) diff --git a/pkg/preferences/memory/memory.go b/pkg/preferences/memory/memory.go new file mode 100644 index 0000000000..b2c285e79d --- /dev/null +++ b/pkg/preferences/memory/memory.go @@ -0,0 +1,79 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package memory + +import ( + "context" + "sync" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" +) + +func init() { + registry.Register("memory", New) +} + +type mgr struct { + sync.RWMutex + keys map[string]map[string]string +} + +// New returns an instance of the in-memory preferences manager. +func New(m map[string]interface{}) (preferences.Manager, error) { + return &mgr{keys: make(map[string]map[string]string)}, nil +} + +func (m *mgr) SetKey(ctx context.Context, key, namespace, value string) error { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return errtypes.UserRequired("preferences: error getting user from ctx") + } + m.Lock() + defer m.Unlock() + + userKey := u.Id.OpaqueId + + if len(m.keys[userKey]) == 0 { + m.keys[userKey] = map[string]string{key: value} + } else { + m.keys[userKey][key] = value + } + return nil +} + +func (m *mgr) GetKey(ctx context.Context, key, namespace string) (string, error) { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return "", errtypes.UserRequired("preferences: error getting user from ctx") + } + m.RLock() + defer m.RUnlock() + + userKey := u.Id.OpaqueId + + if len(m.keys[userKey]) != 0 { + if value, ok := m.keys[userKey][key]; ok { + return value, nil + } + } + return "", errtypes.NotFound("preferences: key not found") +} diff --git a/pkg/preferences/preferences.go b/pkg/preferences/preferences.go new file mode 100644 index 0000000000..4f417396cb --- /dev/null +++ b/pkg/preferences/preferences.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package preferences + +import ( + "context" +) + +// Manager defines an interface for a preferences manager. +type Manager interface { + // SetKey sets a key under a specified namespace. + SetKey(ctx context.Context, key, namespace, value string) error + // GetKey returns the value for a combination of key and namespace, if set. + GetKey(ctx context.Context, key, namespace string) (string, error) +} diff --git a/pkg/preferences/registry/registry.go b/pkg/preferences/registry/registry.go new file mode 100644 index 0000000000..a8ca108f2d --- /dev/null +++ b/pkg/preferences/registry/registry.go @@ -0,0 +1,34 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package registry + +import "github.com/cs3org/reva/pkg/preferences" + +// NewFunc is the function that preferences implementations +// should register at init time. +type NewFunc func(map[string]interface{}) (preferences.Manager, error) + +// NewFuncs is a map containing all the registered preferences implementations. +var NewFuncs = map[string]NewFunc{} + +// Register registers a new preferences function. +// Not safe for concurrent use. Safe for use from package init. +func Register(name string, f NewFunc) { + NewFuncs[name] = f +} diff --git a/pkg/share/cache/cache.go b/pkg/share/cache/cache.go index 8e2023873b..a17791630c 100644 --- a/pkg/share/cache/cache.go +++ b/pkg/share/cache/cache.go @@ -19,6 +19,8 @@ package cache import ( + "time" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ) @@ -26,3 +28,11 @@ import ( type Warmup interface { GetResourceInfos() ([]*provider.ResourceInfo, error) } + +// ResourceInfoCache is the interface to implement caches for resource infos +type ResourceInfoCache interface { + Get(key string) (*provider.ResourceInfo, error) + GetKeys(keys []string) ([]*provider.ResourceInfo, error) + Set(key string, info *provider.ResourceInfo) error + SetWithExpire(key string, info *provider.ResourceInfo, expiration time.Duration) error +} diff --git a/pkg/share/cache/loader/loader.go b/pkg/share/cache/loader/loader.go index 641d89f08c..32a6bf133e 100644 --- a/pkg/share/cache/loader/loader.go +++ b/pkg/share/cache/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load share cache drivers. - _ "github.com/cs3org/reva/v2/pkg/share/cache/cbox" + _ "github.com/cs3org/reva/pkg/share/cache/memory" + _ "github.com/cs3org/reva/pkg/share/cache/redis" // Add your own here ) diff --git a/pkg/share/cache/memory/memory.go b/pkg/share/cache/memory/memory.go new file mode 100644 index 0000000000..b0faa70b1a --- /dev/null +++ b/pkg/share/cache/memory/memory.go @@ -0,0 +1,83 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package memory + +import ( + "time" + + "github.com/bluele/gcache" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/share/cache" + "github.com/cs3org/reva/pkg/share/cache/registry" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +func init() { + registry.Register("memory", New) +} + +type config struct { + CacheSize int `mapstructure:"cache_size"` +} + +type manager struct { + cache gcache.Cache +} + +// New returns an implementation of a resource info cache that stores the objects in memory +func New(m map[string]interface{}) (cache.ResourceInfoCache, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, errors.Wrap(err, "error decoding conf") + } + if c.CacheSize == 0 { + c.CacheSize = 1000000 + } + + return &manager{ + cache: gcache.New(c.CacheSize).LFU().Build(), + }, nil +} + +func (m *manager) Get(key string) (*provider.ResourceInfo, error) { + infoIf, err := m.cache.Get(key) + if err != nil { + return nil, err + } + return infoIf.(*provider.ResourceInfo), nil +} + +func (m *manager) GetKeys(keys []string) ([]*provider.ResourceInfo, error) { + infos := make([]*provider.ResourceInfo, len(keys)) + for i, key := range keys { + if infoIf, err := m.cache.Get(key); err == nil { + infos[i] = infoIf.(*provider.ResourceInfo) + } + } + return infos, nil +} + +func (m *manager) Set(key string, info *provider.ResourceInfo) error { + return m.cache.Set(key, info) +} + +func (m *manager) SetWithExpire(key string, info *provider.ResourceInfo, expiration time.Duration) error { + return m.cache.SetWithExpire(key, info, expiration) +} diff --git a/pkg/share/cache/redis/redis.go b/pkg/share/cache/redis/redis.go new file mode 100644 index 0000000000..e71a457f01 --- /dev/null +++ b/pkg/share/cache/redis/redis.go @@ -0,0 +1,153 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package redis + +import ( + "encoding/json" + "time" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/share/cache" + "github.com/cs3org/reva/pkg/share/cache/registry" + "github.com/gomodule/redigo/redis" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +func init() { + registry.Register("redis", New) +} + +type config struct { + RedisAddress string `mapstructure:"redis_address"` + RedisUsername string `mapstructure:"redis_username"` + RedisPassword string `mapstructure:"redis_password"` +} + +type manager struct { + redisPool *redis.Pool +} + +// New returns an implementation of a resource info cache that stores the objects in a redis cluster +func New(m map[string]interface{}) (cache.ResourceInfoCache, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, errors.Wrap(err, "error decoding conf") + } + + if c.RedisAddress == "" { + c.RedisAddress = "localhost:6379" + } + + pool := &redis.Pool{ + MaxIdle: 50, + MaxActive: 1000, + IdleTimeout: 240 * time.Second, + + Dial: func() (redis.Conn, error) { + var opts []redis.DialOption + if c.RedisUsername != "" { + opts = append(opts, redis.DialUsername(c.RedisUsername)) + } + if c.RedisPassword != "" { + opts = append(opts, redis.DialPassword(c.RedisPassword)) + } + + c, err := redis.Dial("tcp", c.RedisAddress, opts...) + if err != nil { + return nil, err + } + return c, err + }, + + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + + return &manager{ + redisPool: pool, + }, nil +} + +func (m *manager) Get(key string) (*provider.ResourceInfo, error) { + infos, err := m.getVals([]string{key}) + if err != nil { + return nil, err + } + return infos[0], nil +} + +func (m *manager) GetKeys(keys []string) ([]*provider.ResourceInfo, error) { + return m.getVals(keys) +} + +func (m *manager) Set(key string, info *provider.ResourceInfo) error { + return m.setVal(key, info, -1) +} + +func (m *manager) SetWithExpire(key string, info *provider.ResourceInfo, expiration time.Duration) error { + return m.setVal(key, info, int(expiration.Seconds())) +} + +func (m *manager) setVal(key string, info *provider.ResourceInfo, expiration int) error { + conn := m.redisPool.Get() + defer conn.Close() + if conn != nil { + encodedInfo, err := json.Marshal(&info) + if err != nil { + return err + } + + args := []interface{}{key, encodedInfo} + if expiration != -1 { + args = append(args, "EX", expiration) + } + + if _, err := conn.Do("SET", args); err != nil { + return err + } + return nil + } + return errors.New("cache: unable to get connection from redis pool") +} + +func (m *manager) getVals(keys []string) ([]*provider.ResourceInfo, error) { + conn := m.redisPool.Get() + defer conn.Close() + + if conn != nil { + vals, err := redis.Strings(conn.Do("MGET", keys)) + if err != nil { + return nil, err + } + + infos := make([]*provider.ResourceInfo, len(keys)) + for i, v := range vals { + if v != "" { + if err = json.Unmarshal([]byte(v), &infos[i]); err != nil { + infos[i] = nil + } + } + } + return infos, nil + } + return nil, errors.New("cache: unable to get connection from redis pool") +} diff --git a/pkg/share/cache/registry/registry.go b/pkg/share/cache/registry/registry.go index 8f1b72d6a8..fe97a152a3 100644 --- a/pkg/share/cache/registry/registry.go +++ b/pkg/share/cache/registry/registry.go @@ -20,14 +20,14 @@ package registry import "github.com/cs3org/reva/v2/pkg/share/cache" -// NewFunc is the function that cache warmup implementations +// NewFunc is the function that cache implementations // should register at init time. -type NewFunc func(map[string]interface{}) (cache.Warmup, error) +type NewFunc func(map[string]interface{}) (cache.ResourceInfoCache, error) -// NewFuncs is a map containing all the registered cache warmup implementations. +// NewFuncs is a map containing all the registered cache implementations. var NewFuncs = map[string]NewFunc{} -// Register registers a new cache warmup function. +// Register registers a new cache function. // Not safe for concurrent use. Safe for use from package init. func Register(name string, f NewFunc) { NewFuncs[name] = f diff --git a/pkg/share/cache/cbox/cbox.go b/pkg/share/cache/warmup/cbox/cbox.go similarity index 90% rename from pkg/share/cache/cbox/cbox.go rename to pkg/share/cache/warmup/cbox/cbox.go index 45137b2ecb..7723eb138e 100644 --- a/pkg/share/cache/cbox/cbox.go +++ b/pkg/share/cache/warmup/cbox/cbox.go @@ -26,12 +26,12 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/share/cache" - "github.com/cs3org/reva/v2/pkg/share/cache/registry" - "github.com/cs3org/reva/v2/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/share/cache" + "github.com/cs3org/reva/pkg/share/cache/warmup/registry" + "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/grpc/metadata" @@ -69,7 +69,7 @@ func parseConfig(m map[string]interface{}) (*config, error) { return c, nil } -// New returns a new implementation of the storage.FS interface that connects to EOS. +// New returns an implementation of cache warmup that connects to the cbox share db and stats resources on EOS func New(m map[string]interface{}) (cache.Warmup, error) { c, err := parseConfig(m) if err != nil { diff --git a/pkg/share/cache/warmup/loader/loader.go b/pkg/share/cache/warmup/loader/loader.go new file mode 100644 index 0000000000..e21d31aad4 --- /dev/null +++ b/pkg/share/cache/warmup/loader/loader.go @@ -0,0 +1,25 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package loader + +import ( + // Load share cache drivers. + _ "github.com/cs3org/reva/pkg/share/cache/warmup/cbox" + // Add your own here +) diff --git a/pkg/share/cache/warmup/registry/registry.go b/pkg/share/cache/warmup/registry/registry.go new file mode 100644 index 0000000000..ce9dff08b5 --- /dev/null +++ b/pkg/share/cache/warmup/registry/registry.go @@ -0,0 +1,34 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package registry + +import "github.com/cs3org/reva/pkg/share/cache" + +// NewFunc is the function that cache warmup implementations +// should register at init time. +type NewFunc func(map[string]interface{}) (cache.Warmup, error) + +// NewFuncs is a map containing all the registered cache warmup implementations. +var NewFuncs = map[string]NewFunc{} + +// Register registers a new cache warmup function. +// Not safe for concurrent use. Safe for use from package init. +func Register(name string, f NewFunc) { + NewFuncs[name] = f +} diff --git a/pkg/share/manager/loader/loader.go b/pkg/share/manager/loader/loader.go index b29c6ad31a..b3ebbcae29 100644 --- a/pkg/share/manager/loader/loader.go +++ b/pkg/share/manager/loader/loader.go @@ -20,9 +20,8 @@ package loader import ( // Load core share manager drivers. - _ "github.com/cs3org/reva/v2/pkg/share/manager/cs3" - _ "github.com/cs3org/reva/v2/pkg/share/manager/json" - _ "github.com/cs3org/reva/v2/pkg/share/manager/memory" - _ "github.com/cs3org/reva/v2/pkg/share/manager/owncloudsql" + _ "github.com/cs3org/reva/pkg/share/manager/json" + _ "github.com/cs3org/reva/pkg/share/manager/memory" + _ "github.com/cs3org/reva/pkg/share/manager/sql" // Add your own here ) diff --git a/pkg/share/manager/owncloudsql/conversions.go b/pkg/share/manager/owncloudsql/conversions.go index ee34dbefbb..cbbc611ee7 100644 --- a/pkg/share/manager/owncloudsql/conversions.go +++ b/pkg/share/manager/owncloudsql/conversions.go @@ -100,7 +100,8 @@ func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *use return "", err } getUserResponse, err := gwConn.GetUser(ctx, &userprovider.GetUserRequest{ - UserId: userid, + UserId: userid, + SkipFetchingUserGroups: true, }) if err != nil { return "", err @@ -124,8 +125,9 @@ func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username st return nil, err } getUserResponse, err := gwConn.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: username, + Claim: "username", + Value: username, + SkipFetchingUserGroups: true, }) if err != nil { return nil, err diff --git a/pkg/siteacc/account/contact/contact.go b/pkg/siteacc/account/contact/contact.go index d92d1546a2..307d5d0996 100644 --- a/pkg/siteacc/account/contact/contact.go +++ b/pkg/siteacc/account/contact/contact.go @@ -32,7 +32,7 @@ func (template *PanelTemplate) GetTitle() string { // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Contact the ScienceMesh administration" + return "Contact the ScienceMesh administration!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/edit/edit.go b/pkg/siteacc/account/edit/edit.go index 58a4375a05..8a01678f92 100644 --- a/pkg/siteacc/account/edit/edit.go +++ b/pkg/siteacc/account/edit/edit.go @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Account" + return "ScienceMesh Site Administrator Account" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Edit your ScienceMesh Account!" + return "Edit your ScienceMesh Site Administrator Account!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/edit/template.go b/pkg/siteacc/account/edit/template.go index 0df4310aee..558f0295bd 100644 --- a/pkg/siteacc/account/edit/template.go +++ b/pkg/siteacc/account/edit/template.go @@ -99,7 +99,7 @@ html * { const tplBody = `
-

Edit your ScienceMesh account information below.

+

Edit your ScienceMesh Site Administrator Account information below.

Please note that you cannot modify your email address using this form.

 
diff --git a/pkg/siteacc/account/login/login.go b/pkg/siteacc/account/login/login.go index dc1df68278..af219fbb2d 100644 --- a/pkg/siteacc/account/login/login.go +++ b/pkg/siteacc/account/login/login.go @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Account Login" + return "ScienceMesh Site Administrator Account Login" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Login to your ScienceMesh Account!" + return "Login to your ScienceMesh Site Administrator Account!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go index 501ff5609d..c47fe0edf2 100644 --- a/pkg/siteacc/account/login/template.go +++ b/pkg/siteacc/account/login/template.go @@ -109,7 +109,7 @@ html * { const tplBody = `
-

Login to your ScienceMesh account using the form below.

+

Login to your ScienceMesh Site Administrator Account using the form below.

 
diff --git a/pkg/siteacc/account/manage/manage.go b/pkg/siteacc/account/manage/manage.go index cd3e444efd..393ea29079 100644 --- a/pkg/siteacc/account/manage/manage.go +++ b/pkg/siteacc/account/manage/manage.go @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Account" + return "ScienceMesh Site Administrator Account" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Welcome to your ScienceMesh Account!" + return "Welcome to your ScienceMesh Site Administrator Account!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/account/manage/template.go index 9fe3db7f2b..5dc597dca7 100644 --- a/pkg/siteacc/account/manage/template.go +++ b/pkg/siteacc/account/manage/template.go @@ -19,7 +19,7 @@ package manage const tplJavaScript = ` -function handleSettings() { +function handleAccountSettings() { setState(STATE_STATUS, "Redirecting to the account settings..."); window.location.replace("{{getServerAddress}}/account/?path=settings"); } @@ -29,14 +29,14 @@ function handleEditAccount() { window.location.replace("{{getServerAddress}}/account/?path=edit"); } -function handleRequestAccess() { - setState(STATE_STATUS, "Redirecting to the contact form..."); - window.location.replace("{{getServerAddress}}/account/?path=contact&subject=" + encodeURIComponent("Request GOCDB access")); +function handleSiteSettings() { + setState(STATE_STATUS, "Redirecting to the site settings..."); + window.location.replace("{{getServerAddress}}/account/?path=site"); } -function handleRequestKey() { - setState(STATE_STATUS, "Redirecting to the contact form..."); - window.location.replace("{{getServerAddress}}/account/?path=contact&subject=" + encodeURIComponent("Request API key")); +function handleRequestAccess(scope) { + setState(STATE_STATUS, "Redirecting to the contact form..."); + window.location.replace("{{getServerAddress}}/account/?path=contact&subject=" + encodeURIComponent("Request " + scope + " access")); } function handleLogout() { @@ -63,19 +63,15 @@ const tplStyleSheet = ` html * { font-family: arial !important; } - -.apikey { - font-family: monospace !important; - background: antiquewhite; - border: 1px solid black; - padding: 2px; +button { + min-width: 170px; } ` const tplBody = `

Hello {{.Account.FirstName}} {{.Account.LastName}},

-

On this page, you can manage your ScienceMesh user account. This includes editing your personal information, requesting an API key or access to the GOCDB and more.

+

On this page, you can manage your ScienceMesh Site Administrator Account. This includes editing your personal information, requesting access to the GOCDB and more.

 
@@ -92,20 +88,45 @@ const tplBody = `
Account data: -
    -
  • API Key: {{if .Account.Data.APIKey}}{{.Account.Data.APIKey}}{{else}}(no key assigned yet){{end}}
  • -
  • GOCDB access: {{if .Account.Data.GOCDBAccess}}Granted{{else}}Not granted{{end}}
  • +
      +
    • Site access: {{if .Account.Data.SiteAccess}}Granted{{else}}Not granted{{end}}
    • +
    • GOCDB access: {{if .Account.Data.GOCDBAccess}}Granted{{else}}Not granted{{end}}
- - -   - - - - +
+ + +   + + {{if .Account.Data.SiteAccess}} + +   + {{end}} + + +
+
+ + +
+
+
+
Notes:
+
    +
  • The Site access allows you to access and modify the global configuration of your site.
  • +
  • The GOCDB access allows you to log into the central database where all site metadata is stored.
  • +
+
+ +
` diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go index 6bdd87157c..a3c29ef55e 100644 --- a/pkg/siteacc/account/panel.go +++ b/pkg/siteacc/account/panel.go @@ -23,15 +23,16 @@ import ( "net/url" "strings" - "github.com/cs3org/reva/v2/pkg/siteacc/account/contact" - "github.com/cs3org/reva/v2/pkg/siteacc/account/edit" - "github.com/cs3org/reva/v2/pkg/siteacc/account/login" - "github.com/cs3org/reva/v2/pkg/siteacc/account/manage" - "github.com/cs3org/reva/v2/pkg/siteacc/account/registration" - "github.com/cs3org/reva/v2/pkg/siteacc/account/settings" - "github.com/cs3org/reva/v2/pkg/siteacc/config" - "github.com/cs3org/reva/v2/pkg/siteacc/data" - "github.com/cs3org/reva/v2/pkg/siteacc/html" + "github.com/cs3org/reva/pkg/siteacc/account/contact" + "github.com/cs3org/reva/pkg/siteacc/account/edit" + "github.com/cs3org/reva/pkg/siteacc/account/login" + "github.com/cs3org/reva/pkg/siteacc/account/manage" + "github.com/cs3org/reva/pkg/siteacc/account/registration" + "github.com/cs3org/reva/pkg/siteacc/account/settings" + "github.com/cs3org/reva/pkg/siteacc/account/site" + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -50,6 +51,7 @@ const ( templateManage = "manage" templateSettings = "settings" templateEdit = "edit" + templateSite = "site" templateContact = "contact" templateRegistration = "register" ) @@ -84,6 +86,10 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) return errors.Wrap(err, "unable to create the account editing template") } + if err := panel.htmlPanel.AddTemplate(templateSite, &site.PanelTemplate{}); err != nil { + return errors.Wrap(err, "unable to create the site template") + } + if err := panel.htmlPanel.AddTemplate(templateContact, &contact.PanelTemplate{}); err != nil { return errors.Wrap(err, "unable to create the contact template") } @@ -97,7 +103,7 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) // GetActiveTemplate returns the name of the active template. func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string { - validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateContact, templateRegistration} + validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateSite, templateContact, templateRegistration} template := templateLogin // Only allow valid template paths; redirect to the login page otherwise @@ -113,18 +119,28 @@ func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string // PreExecute is called before the actual template is being executed. func (panel *Panel) PreExecute(session *html.Session, path string, w http.ResponseWriter, r *http.Request) (html.ExecutionResult, error) { - protectedPaths := []string{templateManage, templateSettings, templateEdit, templateContact} + protectedPaths := []string{templateManage, templateSettings, templateEdit, templateSite, templateContact} + + if user := session.LoggedInUser(); user != nil { + switch path { + case templateSite: + // If the logged in user doesn't have site access, redirect him back to the main account page + if !user.Account.Data.SiteAccess { + return panel.redirect(templateManage, w, r), nil + } - if session.LoggedInUser == nil { + case templateLogin: + case templateRegistration: + // If a user is logged in and tries to login or register again, redirect to the main account page + return panel.redirect(templateManage, w, r), nil + } + } else { // If no user is logged in, redirect protected paths to the login page for _, protected := range protectedPaths { if protected == path { return panel.redirect(templateLogin, w, r), nil } } - } else if path == templateLogin || path == templateRegistration { - // If a user is logged in and tries to login or register again, redirect to the main account page - return panel.redirect(templateManage, w, r), nil } return html.ContinueExecution, nil @@ -144,6 +160,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *htm } type TemplateData struct { + Site *data.Site Account *data.Account Params map[string]string @@ -151,12 +168,18 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *htm Sites []data.SiteInformation } - return TemplateData{ - Account: session.LoggedInUser, + tplData := TemplateData{ + Site: nil, + Account: nil, Params: flatValues, Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, Sites: availSites, } + if user := session.LoggedInUser(); user != nil { + tplData.Site = panel.cloneUserSite(user.Site) + tplData.Account = user.Account + } + return tplData } return panel.htmlPanel.Execute(w, r, session, dataProvider) } @@ -179,11 +202,22 @@ func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request return html.AbortExecution } +func (panel *Panel) cloneUserSite(site *data.Site) *data.Site { + // Clone the user's site and decrypt the credentials for the panel + siteClone := site.Clone(true) + id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) + if err == nil { + siteClone.Config.TestClientCredentials.ID = id + siteClone.Config.TestClientCredentials.Secret = secret + } + return siteClone +} + // NewPanel creates a new account panel. func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { form := &Panel{} if err := form.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the account panel") + return nil, errors.Wrap(err, "unable to initialize the account panel") } return form, nil } diff --git a/pkg/siteacc/account/registration/registration.go b/pkg/siteacc/account/registration/registration.go index 4721c8fbf3..a122018881 100644 --- a/pkg/siteacc/account/registration/registration.go +++ b/pkg/siteacc/account/registration/registration.go @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Account Registration" + return "ScienceMesh Site Administrator Account Registration" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Welcome to the ScienceMesh Account Registration!" + return "Welcome to the ScienceMesh Site Administrator Account Registration!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go index 97a4f0b2dd..babab0e52e 100644 --- a/pkg/siteacc/account/registration/template.go +++ b/pkg/siteacc/account/registration/template.go @@ -117,7 +117,7 @@ html * { const tplBody = `
-

Fill out the form below to register for a ScienceMesh account. A confirmation email will be sent to you shortly after registration.

+

Fill out the form below to register for a ScienceMesh Site Administrator account. A confirmation email will be sent to you shortly after registration.

 
diff --git a/pkg/siteacc/account/settings/settings.go b/pkg/siteacc/account/settings/settings.go index 5d225e2abb..7c481c8793 100644 --- a/pkg/siteacc/account/settings/settings.go +++ b/pkg/siteacc/account/settings/settings.go @@ -27,12 +27,12 @@ type PanelTemplate struct { // GetTitle returns the title of the panel. func (template *PanelTemplate) GetTitle() string { - return "ScienceMesh Account" + return "ScienceMesh Site Administrator Account" } // GetCaption returns the caption which is displayed on the panel. func (template *PanelTemplate) GetCaption() string { - return "Configure your ScienceMesh Account!" + return "Configure your ScienceMesh Site Administrator Account!" } // GetContentJavaScript delivers additional JavaScript code. diff --git a/pkg/siteacc/account/settings/template.go b/pkg/siteacc/account/settings/template.go index e3a2d94b69..98fec5d61b 100644 --- a/pkg/siteacc/account/settings/template.go +++ b/pkg/siteacc/account/settings/template.go @@ -66,19 +66,19 @@ input[type="checkbox"] { const tplBody = `
-

Configure your ScienceMesh account below.

+

Configure your ScienceMesh Site Administrator Account below.

 
-
+

Notification settings


- - + +
diff --git a/pkg/siteacc/account/site/site.go b/pkg/siteacc/account/site/site.go new file mode 100644 index 0000000000..fc540a7fa3 --- /dev/null +++ b/pkg/siteacc/account/site/site.go @@ -0,0 +1,51 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package site + +import "github.com/cs3org/reva/pkg/siteacc/html" + +// PanelTemplate is the content provider for the edit form. +type PanelTemplate struct { + html.ContentProvider +} + +// GetTitle returns the title of the panel. +func (template *PanelTemplate) GetTitle() string { + return "ScienceMesh Site Configuration" +} + +// GetCaption returns the caption which is displayed on the panel. +func (template *PanelTemplate) GetCaption() string { + return "Configure your ScienceMesh Site!" +} + +// GetContentJavaScript delivers additional JavaScript code. +func (template *PanelTemplate) GetContentJavaScript() string { + return tplJavaScript +} + +// GetContentStyleSheet delivers additional stylesheet code. +func (template *PanelTemplate) GetContentStyleSheet() string { + return tplStyleSheet +} + +// GetContentBody delivers the actual body content. +func (template *PanelTemplate) GetContentBody() string { + return tplBody +} diff --git a/pkg/siteacc/account/site/template.go b/pkg/siteacc/account/site/template.go new file mode 100644 index 0000000000..fef2c646cc --- /dev/null +++ b/pkg/siteacc/account/site/template.go @@ -0,0 +1,117 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package site + +const tplJavaScript = ` +function verifyForm(formData) { + if (formData.getTrimmed("clientID") == "") { + setState(STATE_ERROR, "Please enter the name of the test user.", "form", "clientID", true); + return false; + } + + if (formData.get("secret") == "") { + setState(STATE_ERROR, "Please enter the password of the test user.", "form", "secret", true); + return false; + } + + return true; +} + +function handleAction(action) { + const formData = new FormData(document.querySelector("form")); + if (!verifyForm(formData)) { + return; + } + + setState(STATE_STATUS, "Configuring site... this should only take a moment.", "form", null, false); + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "{{getServerAddress}}/" + action); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + + xhr.onload = function() { + if (this.status == 200) { + setState(STATE_SUCCESS, "Your site was successfully configured!", "form", null, true); + } else { + var resp = JSON.parse(this.responseText); + setState(STATE_ERROR, "An error occurred while trying to configure your site:
" + resp.error + "", "form", null, true); + } + } + + var postData = { + "config": { + "testClientCredentials": { + "id": formData.getTrimmed("clientID"), + "secret": formData.get("secret") + } + } + }; + + xhr.send(JSON.stringify(postData)); +} +` + +const tplStyleSheet = ` +html * { + font-family: arial !important; +} + +input[type="checkbox"] { + width: auto; +} + +.mandatory { + color: red; + font-weight: bold; +} +` + +const tplBody = ` +
+

Configure your ScienceMesh Site below. These settings affect your entire site and not just your account.

+
+
 
+
+ +
+

Test user settings

+

In order to perform automated tests on your site, a test user has to be configured below. Please note that the user has to exist in your Reva instance! If you do not have a user for automated tests in your instance yet, create one first.

+
+
+ +
+
+
+
+ +
 
+ +
+ Fields marked with * are mandatory. +
+
+ + +
+ +
+
+

Go back to the main account page.

+
+` diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go index 37dea64746..57ee2dac35 100644 --- a/pkg/siteacc/admin/panel.go +++ b/pkg/siteacc/admin/panel.go @@ -63,12 +63,12 @@ func (panel *Panel) GetActiveTemplate(*html.Session, string) string { // GetTitle returns the title of the htmlPanel. func (panel *Panel) GetTitle() string { - return "ScienceMesh Administration Panel" + return "ScienceMesh Site Administrator Accounts Panel" } // GetCaption returns the caption which is displayed on the htmlPanel. func (panel *Panel) GetCaption() string { - return "Accounts ({{.Accounts | len}})" + return "ScienceMesh Site Administrator Accounts ({{.Accounts | len}})" } // GetContentJavaScript delivers additional JavaScript code. @@ -109,7 +109,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *htm func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { panel := &Panel{} if err := panel.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the administration panel") + return nil, errors.Wrap(err, "unable to initialize the administration panel") } return panel, nil } diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go index 6dccaca3df..b1ba600fa3 100644 --- a/pkg/siteacc/admin/template.go +++ b/pkg/siteacc/admin/template.go @@ -50,11 +50,11 @@ html * { ` const tplBody = ` -
+
    {{range .Accounts}}
  • -

    +

    {{.Email}}
    {{.Title}}. {{.FirstName}} {{.LastName}} (Joined: {{.DateCreated.Format "Jan 02, 2006 15:04"}}; Last modified: {{.DateModified.Format "Jan 02, 2006 15:04"}}) @@ -66,41 +66,38 @@ const tplBody = `
  • Phone: {{.PhoneNumber}}
-

-

- API Key: {{if .Data.APIKey}}{{.Data.APIKey}}{{else}}Not assigned{{end}} -
- Site ID: {{.GetSiteID}} -

- Authorized: {{if .Data.Authorized}}Yes{{else}}No{{end}} -
- GOCDB access: {{if .Data.GOCDBAccess}}Granted{{else}}Not granted{{end}} -

-

+

+ +
 
+ +
+ Account data: +
    +
  • Site access: {{if .Data.SiteAccess}}Granted{{else}}Not granted{{end}}
  • +
  • GOCDB access: {{if .Data.GOCDBAccess}}Granted{{else}}Not granted{{end}}
  • +
+
+ +
 
+ +
- - -

+ {{if .Data.SiteAccess}} + + {{else}} + + {{end}} {{if .Data.GOCDBAccess}} {{else}} {{end}} - - {{if .Data.Authorized}} - - {{else}} - - {{end}} - -   -  
-

+

{{end}} diff --git a/pkg/siteacc/alerting/dispatcher.go b/pkg/siteacc/alerting/dispatcher.go index 0561a33269..3d5ceb0240 100644 --- a/pkg/siteacc/alerting/dispatcher.go +++ b/pkg/siteacc/alerting/dispatcher.go @@ -67,13 +67,30 @@ func (dispatcher *Dispatcher) DispatchAlerts(alerts *template.Data, accounts dat // Dispatch the alert to all accounts configured to receive it for _, account := range accounts { - if strings.EqualFold(account.Site, siteID) && account.Settings.ReceiveAlerts { + if strings.EqualFold(account.Site, siteID) /* && account.Settings.ReceiveAlerts */ { // TODO: Uncomment if alert notifications aren't mandatory anymore if err := dispatcher.dispatchAlert(alert, account); err != nil { // Log errors only dispatcher.log.Err(err).Str("id", alert.Fingerprint).Str("recipient", account.Email).Msg("unable to dispatch alert to user") } } } + + // Dispatch the alert to the global receiver (if set) + if dispatcher.conf.Email.NotificationsMail != "" { + globalAccount := data.Account{ // On-the-fly account representing the "global alerts receiver" + Email: dispatcher.conf.Email.NotificationsMail, + FirstName: "ScienceMesh", + LastName: "Global Alerts receiver", + Site: "Global", + Role: "Alerts receiver", + Settings: data.AccountSettings{ + ReceiveAlerts: true, + }, + } + if err := dispatcher.dispatchAlert(alert, &globalAccount); err != nil { + dispatcher.log.Err(err).Str("id", alert.Fingerprint).Str("recipient", globalAccount.Email).Msg("unable to dispatch alert to global alerts receiver") + } + } } return nil } @@ -86,6 +103,7 @@ func (dispatcher *Dispatcher) dispatchAlert(alert template.Alert, account *data. "Fingerprint": alert.Fingerprint, "Name": alert.Labels["alertname"], + "Service": alert.Labels["service_type"], "Instance": alert.Labels["instance"], "Job": alert.Labels["job"], "Severity": alert.Labels["severity"], @@ -96,14 +114,14 @@ func (dispatcher *Dispatcher) dispatchAlert(alert template.Alert, account *data. "Summary": alert.Annotations["summary"], } - return email.SendAlertNotification(account, []string{account.Email, dispatcher.conf.Email.NotificationsMail}, alertValues, *dispatcher.conf) + return email.SendAlertNotification(account, []string{account.Email}, alertValues, *dispatcher.conf) } // NewDispatcher creates a new dispatcher instance. func NewDispatcher(conf *config.Configuration, log *zerolog.Logger) (*Dispatcher, error) { dispatcher := &Dispatcher{} if err := dispatcher.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the alerts dispatcher") + return nil, errors.Wrap(err, "unable to initialize the alerts dispatcher") } return dispatcher, nil } diff --git a/pkg/siteacc/config/config.go b/pkg/siteacc/config/config.go index 0fa5ba2335..835b69e28b 100644 --- a/pkg/siteacc/config/config.go +++ b/pkg/siteacc/config/config.go @@ -28,11 +28,16 @@ import ( type Configuration struct { Prefix string `mapstructure:"prefix"` + Security struct { + CredentialsPassphrase string `mapstructure:"creds_passphrase"` + } `mapstructure:"security"` + Storage struct { Driver string `mapstructure:"driver"` File struct { - File string `mapstructure:"file"` + SitesFile string `mapstructure:"sites_file"` + AccountsFile string `mapstructure:"accounts_file"` } `mapstructure:"file"` } `mapstructure:"storage"` diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go index 9559f135d8..1186ea8994 100644 --- a/pkg/siteacc/config/endpoints.go +++ b/pkg/siteacc/config/endpoints.go @@ -24,13 +24,6 @@ const ( // EndpointAccount is the endpoint path of the web interface account panel. EndpointAccount = "/account" - // EndpointGenerateAPIKey is the endpoint path of the API key generator. - EndpointGenerateAPIKey = "/generate-api-key" - // EndpointVerifyAPIKey is the endpoint path for API key verification. - EndpointVerifyAPIKey = "/verify-api-key" - // EndpointAssignAPIKey is the endpoint path used for assigning an API key to an account. - EndpointAssignAPIKey = "/assign-api-key" - // EndpointList is the endpoint path for listing all stored accounts. EndpointList = "/list" // EndpointFind is the endpoint path for finding accounts. @@ -45,6 +38,11 @@ const ( // EndpointRemove is the endpoint path for account removal. EndpointRemove = "/remove" + // EndpointSiteGet is the endpoint path for retrieving site data. + EndpointSiteGet = "/site-get" + // EndpointSiteConfigure is the endpoint path for site configuration. + EndpointSiteConfigure = "/site-configure" + // EndpointLogin is the endpoint path for (internal) user login. EndpointLogin = "/login" // EndpointLogout is the endpoint path for (internal) user logout. @@ -57,17 +55,11 @@ const ( // EndpointVerifyUserToken is the endpoint path for user token validation. EndpointVerifyUserToken = "/verify-user-token" - // EndpointAuthorize is the endpoint path for account authorization. - EndpointAuthorize = "/authorize" - // EndpointIsAuthorized is the endpoint path used to check the authorization status of an account. - EndpointIsAuthorized = "/is-authorized" - + // EndpointGrantSiteAccess is the endpoint path for granting or revoking Site access. + EndpointGrantSiteAccess = "/grant-site-access" // EndpointGrantGOCDBAccess is the endpoint path for granting or revoking GOCDB access. EndpointGrantGOCDBAccess = "/grant-gocdb-access" - // EndpointUnregisterSite is the endpoint path for site unregistration. - EndpointUnregisterSite = "/unregister-site" - // EndpointDispatchAlert is the endpoint path for dispatching alerts from Prometheus. EndpointDispatchAlert = "/dispatch-alert" ) diff --git a/pkg/siteacc/credentials/credentials.go b/pkg/siteacc/credentials/credentials.go new file mode 100644 index 0000000000..cb67c0685a --- /dev/null +++ b/pkg/siteacc/credentials/credentials.go @@ -0,0 +1,69 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package credentials + +import ( + "github.com/cs3org/reva/pkg/siteacc/credentials/crypto" + "github.com/pkg/errors" +) + +// Credentials stores and en-/decrypts credentials +type Credentials struct { + ID string `json:"id"` + Secret string `json:"secret"` +} + +// Get decrypts and retrieves the stored credentials. +func (creds *Credentials) Get(passphrase string) (string, string, error) { + id, err := crypto.DecodeString(creds.ID, passphrase) + if err != nil { + return "", "", errors.Wrap(err, "unable to decode ID") + } + secret, err := crypto.DecodeString(creds.Secret, passphrase) + if err != nil { + return "", "", errors.Wrap(err, "unable to decode secret") + } + return id, secret, nil +} + +// Set encrypts and sets new credentials. +func (creds *Credentials) Set(id, secret string, passphrase string) error { + if s, err := crypto.EncodeString(id, passphrase); err == nil { + creds.ID = s + } else { + return errors.Wrap(err, "unable to encode ID") + } + if s, err := crypto.EncodeString(secret, passphrase); err == nil { + creds.Secret = s + } else { + return errors.Wrap(err, "unable to encode secret") + } + return nil +} + +// IsValid checks whether the credentials are valid. +func (creds *Credentials) IsValid() bool { + return len(creds.ID) > 0 && len(creds.Secret) > 0 +} + +// Clear resets the credentials. +func (creds *Credentials) Clear() { + creds.ID = "" + creds.Secret = "" +} diff --git a/pkg/siteacc/credentials/crypto/crypto.go b/pkg/siteacc/credentials/crypto/crypto.go new file mode 100644 index 0000000000..af73999942 --- /dev/null +++ b/pkg/siteacc/credentials/crypto/crypto.go @@ -0,0 +1,101 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "io" + + "github.com/pkg/errors" +) + +const ( + passphraseLength = 32 +) + +// EncodeString encodes a string using AES and returns the base64-encoded result. +func EncodeString(s string, passphrase string) (string, error) { + if len(s) == 0 || len(passphrase) == 0 { + return "", nil + } + passphrase = normalizePassphrase(passphrase) + + gcm, err := createGCM([]byte(passphrase)) + if err != nil { + return "", err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return "", errors.Wrap(err, "unable to generate nonce") + } + encryptedData := gcm.Seal(nonce, nonce, []byte(s), nil) + return base64.StdEncoding.EncodeToString(encryptedData), nil +} + +// DecodeString decodes a base64-encoded string encoded with AES. +func DecodeString(s string, passphrase string) (string, error) { + if len(s) == 0 || len(passphrase) == 0 { + return "", nil + } + data, _ := base64.StdEncoding.DecodeString(s) + passphrase = normalizePassphrase(passphrase) + + gcm, err := createGCM([]byte(passphrase)) + if err != nil { + return "", err + } + + nonceSize := gcm.NonceSize() + if len(s) < nonceSize { + return "", errors.Errorf("input string length too short") + } + nonce, data := data[:nonceSize], data[nonceSize:] + plain, err := gcm.Open(nil, nonce, data, nil) + if err != nil { + return "", errors.Wrap(err, "unable to decode string") + } + return string(plain), nil +} + +func createGCM(passphrase []byte) (cipher.AEAD, error) { + c, err := aes.NewCipher(passphrase) + if err != nil { + return nil, errors.Wrap(err, "unable to generate cipher") + } + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, errors.Wrap(err, "unable to generate GCM") + } + return gcm, nil +} + +func normalizePassphrase(passphrase string) string { + if len(passphrase) > passphraseLength { + passphrase = passphrase[:passphraseLength] + } else if len(passphrase) < passphraseLength { + for i := len(passphrase); i < passphraseLength; i++ { + passphrase += "#" + } + } + return passphrase +} diff --git a/pkg/siteacc/password/password.go b/pkg/siteacc/credentials/password.go similarity index 99% rename from pkg/siteacc/password/password.go rename to pkg/siteacc/credentials/password.go index a9a59467e4..f9ca8462e6 100644 --- a/pkg/siteacc/password/password.go +++ b/pkg/siteacc/credentials/password.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package password +package credentials import ( "strings" diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go index c760148edf..8ea5f10912 100644 --- a/pkg/siteacc/data/account.go +++ b/pkg/siteacc/data/account.go @@ -22,11 +22,10 @@ import ( "strings" "time" - "github.com/cs3org/reva/v2/pkg/siteacc/password" + "github.com/cs3org/reva/pkg/siteacc/credentials" "github.com/pkg/errors" - "github.com/cs3org/reva/v2/pkg/mentix/key" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/utils" ) // Account represents a single site account. @@ -39,7 +38,7 @@ type Account struct { Role string `json:"role"` PhoneNumber string `json:"phoneNumber"` - Password password.Password `json:"password"` + Password credentials.Password `json:"password"` DateCreated time.Time `json:"dateCreated"` DateModified time.Time `json:"dateModified"` @@ -50,9 +49,8 @@ type Account struct { // AccountData holds additional data for a site account. type AccountData struct { - APIKey key.APIKey `json:"apiKey"` - GOCDBAccess bool `json:"gocdbAccess"` - Authorized bool `json:"authorized"` + GOCDBAccess bool `json:"gocdbAccess"` + SiteAccess bool `json:"siteAccess"` } // AccountSettings holds additional settings for a site account. @@ -63,15 +61,6 @@ type AccountSettings struct { // Accounts holds an array of site accounts. type Accounts = []*Account -// GetSiteID returns the site ID (generated from the API key) for the given account. -func (acc *Account) GetSiteID() key.SiteIdentifier { - if id, err := key.CalculateSiteID(acc.Data.APIKey, key.SaltFromEmail(acc.Email)); err == nil { - return id - } - - return "" -} - // Update copies the data of the given account to this account. func (acc *Account) Update(other *Account, setPassword bool, copyData bool) error { if err := other.verify(false, false); err != nil { @@ -136,6 +125,9 @@ func (acc *Account) CheckScopeAccess(scope string) bool { case ScopeGOCDB: hasAccess = acc.Data.GOCDBAccess + + case ScopeSite: + hasAccess = acc.Data.SiteAccess } return hasAccess @@ -209,12 +201,11 @@ func NewAccount(email string, title, firstName, lastName string, site, role stri DateCreated: t, DateModified: t, Data: AccountData{ - APIKey: "", GOCDBAccess: false, - Authorized: false, + SiteAccess: false, }, Settings: AccountSettings{ - ReceiveAlerts: false, + ReceiveAlerts: true, }, } diff --git a/pkg/siteacc/data/filestorage.go b/pkg/siteacc/data/filestorage.go index 221f73f022..3e360e8223 100644 --- a/pkg/siteacc/data/filestorage.go +++ b/pkg/siteacc/data/filestorage.go @@ -36,7 +36,8 @@ type FileStorage struct { conf *config.Configuration log *zerolog.Logger - filePath string + sitesFilePath string + accountsFilePath string } func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog.Logger) error { @@ -50,66 +51,115 @@ func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog. } storage.log = log - if conf.Storage.File.File == "" { - return errors.Errorf("no file set in the configuration") + if conf.Storage.File.SitesFile == "" { + return errors.Errorf("no sites file set in the configuration") } - storage.filePath = conf.Storage.File.File + storage.sitesFilePath = conf.Storage.File.SitesFile - // Create the file directory if necessary - dir := filepath.Dir(storage.filePath) - _ = os.MkdirAll(dir, 0755) + if conf.Storage.File.AccountsFile == "" { + return errors.Errorf("no accounts file set in the configuration") + } + storage.accountsFilePath = conf.Storage.File.AccountsFile + + // Create the file directories if necessary + _ = os.MkdirAll(filepath.Dir(storage.sitesFilePath), 0755) + _ = os.MkdirAll(filepath.Dir(storage.accountsFilePath), 0755) return nil } -// ReadAll reads all stored accounts into the given data object. -func (storage *FileStorage) ReadAll() (*Accounts, error) { - accounts := &Accounts{} - - // Read the data from the configured file - jsonData, err := ioutil.ReadFile(storage.filePath) +func (storage *FileStorage) readData(file string, obj interface{}) error { + // Read the data from the specified file + jsonData, err := ioutil.ReadFile(file) if err != nil { - return nil, errors.Wrapf(err, "unable to read file %v", storage.filePath) + return errors.Wrapf(err, "unable to read file %v", file) } - if err := json.Unmarshal(jsonData, accounts); err != nil { - return nil, errors.Wrapf(err, "invalid file %v", storage.filePath) + if err := json.Unmarshal(jsonData, obj); err != nil { + return errors.Wrapf(err, "invalid file %v", file) } + return nil +} + +// ReadSites reads all stored sites into the given data object. +func (storage *FileStorage) ReadSites() (*Sites, error) { + sites := &Sites{} + if err := storage.readData(storage.sitesFilePath, sites); err != nil { + return nil, errors.Wrap(err, "error reading sites") + } + return sites, nil +} + +// ReadAccounts reads all stored accounts into the given data object. +func (storage *FileStorage) ReadAccounts() (*Accounts, error) { + accounts := &Accounts{} + if err := storage.readData(storage.accountsFilePath, accounts); err != nil { + return nil, errors.Wrap(err, "error reading accounts") + } return accounts, nil } -// WriteAll writes all stored accounts from the given data object. -func (storage *FileStorage) WriteAll(accounts *Accounts) error { - // Write the data to the configured file - jsonData, _ := json.MarshalIndent(accounts, "", "\t") - if err := ioutil.WriteFile(storage.filePath, jsonData, 0755); err != nil { - return errors.Wrapf(err, "unable to write file %v", storage.filePath) +func (storage *FileStorage) writeData(file string, obj interface{}) error { + // Write the data to the specified file + jsonData, _ := json.MarshalIndent(obj, "", "\t") + if err := ioutil.WriteFile(file, jsonData, 0755); err != nil { + return errors.Wrapf(err, "unable to write file %v", file) } + return nil +} + +// WriteSites writes all stored sites from the given data object. +func (storage *FileStorage) WriteSites(sites *Sites) error { + if err := storage.writeData(storage.sitesFilePath, sites); err != nil { + return errors.Wrap(err, "error writing sites") + } + return nil +} +// WriteAccounts writes all stored accounts from the given data object. +func (storage *FileStorage) WriteAccounts(accounts *Accounts) error { + if err := storage.writeData(storage.accountsFilePath, accounts); err != nil { + return errors.Wrap(err, "error writing accounts") + } return nil } +// SiteAdded is called when a site has been added. +func (storage *FileStorage) SiteAdded(site *Site) { + // Simply skip this action; all data is saved solely in WriteSites +} + +// SiteUpdated is called when a site has been updated. +func (storage *FileStorage) SiteUpdated(site *Site) { + // Simply skip this action; all data is saved solely in WriteSites +} + +// SiteRemoved is called when a site has been removed. +func (storage *FileStorage) SiteRemoved(site *Site) { + // Simply skip this action; all data is saved solely in WriteSites +} + // AccountAdded is called when an account has been added. func (storage *FileStorage) AccountAdded(account *Account) { - // Simply skip this action; all data is saved solely in WriteAll + // Simply skip this action; all data is saved solely in WriteAccounts } // AccountUpdated is called when an account has been updated. func (storage *FileStorage) AccountUpdated(account *Account) { - // Simply skip this action; all data is saved solely in WriteAll + // Simply skip this action; all data is saved solely in WriteAccounts } // AccountRemoved is called when an account has been removed. func (storage *FileStorage) AccountRemoved(account *Account) { - // Simply skip this action; all data is saved solely in WriteAll + // Simply skip this action; all data is saved solely in WriteAccounts } -// NewFileStorage creates a new filePath storage. +// NewFileStorage creates a new file storage. func NewFileStorage(conf *config.Configuration, log *zerolog.Logger) (*FileStorage, error) { storage := &FileStorage{} if err := storage.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the filePath storage") + return nil, errors.Wrap(err, "unable to initialize the file storage") } return storage, nil } diff --git a/pkg/siteacc/data/scopes.go b/pkg/siteacc/data/scopes.go index f515fb68f1..438c9a1c53 100644 --- a/pkg/siteacc/data/scopes.go +++ b/pkg/siteacc/data/scopes.go @@ -23,4 +23,6 @@ const ( ScopeDefault = "" // ScopeGOCDB is used to access the GOCDB. ScopeGOCDB = "gocdb" + // ScopeSite is used to access the global site configuration. + ScopeSite = "site" ) diff --git a/pkg/siteacc/data/site.go b/pkg/siteacc/data/site.go new file mode 100644 index 0000000000..8e003d985d --- /dev/null +++ b/pkg/siteacc/data/site.go @@ -0,0 +1,81 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package data + +import ( + "github.com/cs3org/reva/pkg/siteacc/credentials" + "github.com/pkg/errors" +) + +// Site represents the global site-specific settings stored in the service. +type Site struct { + ID string `json:"id"` + + Config SiteConfiguration `json:"config"` +} + +// SiteConfiguration stores the global configuration of a site. +type SiteConfiguration struct { + TestClientCredentials credentials.Credentials `json:"testClientCredentials"` +} + +// Sites holds an array of sites. +type Sites = []*Site + +// Update copies the data of the given site to this site. +func (site *Site) Update(other *Site, credsPassphrase string) error { + if other.Config.TestClientCredentials.IsValid() { + // If credentials were provided, use those as the new ones + if err := site.UpdateTestClientCredentials(other.Config.TestClientCredentials.ID, other.Config.TestClientCredentials.Secret, credsPassphrase); err != nil { + return err + } + } + + return nil +} + +// UpdateTestClientCredentials assigns new test client credentials, encrypting the information first. +func (site *Site) UpdateTestClientCredentials(id, secret string, passphrase string) error { + if err := site.Config.TestClientCredentials.Set(id, secret, passphrase); err != nil { + return errors.Wrap(err, "unable to update the test client credentials") + } + return nil +} + +// Clone creates a copy of the site; if eraseCredentials is set to true, the (test user) credentials will be cleared in the cloned object. +func (site *Site) Clone(eraseCredentials bool) *Site { + clone := *site + + if eraseCredentials { + clone.Config.TestClientCredentials.Clear() + } + + return &clone +} + +// NewSite creates a new site. +func NewSite(id string) (*Site, error) { + site := &Site{ + ID: id, + Config: SiteConfiguration{ + TestClientCredentials: credentials.Credentials{}, + }, + } + return site, nil +} diff --git a/pkg/siteacc/data/sites.go b/pkg/siteacc/data/siteinfo.go similarity index 100% rename from pkg/siteacc/data/sites.go rename to pkg/siteacc/data/siteinfo.go diff --git a/pkg/siteacc/data/storage.go b/pkg/siteacc/data/storage.go index 221e7bcb32..7d770a3f2f 100644 --- a/pkg/siteacc/data/storage.go +++ b/pkg/siteacc/data/storage.go @@ -18,12 +18,24 @@ package data -// Storage defines the interface for accounts storages. +// Storage defines the interface for sites and accounts storages. type Storage interface { - // ReadAll reads all stored accounts into the given data object. - ReadAll() (*Accounts, error) - // WriteAll writes all stored accounts from the given data object. - WriteAll(accounts *Accounts) error + // ReadSites reads all stored sites into the given data object. + ReadSites() (*Sites, error) + // WriteSites writes all stored sites from the given data object. + WriteSites(sites *Sites) error + + // SiteAdded is called when a site has been added. + SiteAdded(site *Site) + // SiteUpdated is called when a site has been updated. + SiteUpdated(site *Site) + // SiteRemoved is called when a site has been removed. + SiteRemoved(site *Site) + + // ReadAccounts reads all stored accounts into the given data object. + ReadAccounts() (*Accounts, error) + // WriteAccounts writes all stored accounts from the given data object. + WriteAccounts(accounts *Accounts) error // AccountAdded is called when an account has been added. AccountAdded(account *Account) diff --git a/pkg/siteacc/email/email.go b/pkg/siteacc/email/email.go index 83eef81375..9f249627e4 100644 --- a/pkg/siteacc/email/email.go +++ b/pkg/siteacc/email/email.go @@ -52,17 +52,12 @@ func getEmailData(account *data.Account, conf config.Configuration, params map[s // SendAccountCreated sends an email about account creation. func SendAccountCreated(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { - return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) + return send(recipients, "ScienceMesh: Site Administrator Account created", accountCreatedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) } -// SendAPIKeyAssigned sends an email about API key assignment. -func SendAPIKeyAssigned(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { - return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) -} - -// SendAccountAuthorized sends an email about account authorization. -func SendAccountAuthorized(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { - return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) +// SendSiteAccessGranted sends an email about granted Site access. +func SendSiteAccessGranted(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error { + return send(recipients, "ScienceMesh: Site access granted", siteAccessGrantedTemplate, getEmailData(account, conf, params), conf.Email.SMTP) } // SendGOCDBAccessGranted sends an email about granted GOCDB access. diff --git a/pkg/siteacc/email/template.go b/pkg/siteacc/email/template.go index 3f3bf8e026..6912d1dfb2 100644 --- a/pkg/siteacc/email/template.go +++ b/pkg/siteacc/email/template.go @@ -21,35 +21,24 @@ package email const accountCreatedTemplate = ` Dear {{.Account.FirstName}} {{.Account.LastName}}, -Your ScienceMesh account has been successfully created! +Your ScienceMesh Site Administrator Account has been successfully created! Log in to your account by visiting the user account panel: {{.AccountsAddress}} -Using this panel, you can modify your information, request an API key or access to the GOCDB, and more. +Using this panel, you can modify your information, request access to the GOCDB, and more. Kind regards, The ScienceMesh Team ` -const apiKeyAssignedTemplate = ` +const siteAccessGrantedTemplate = ` Dear {{.Account.FirstName}} {{.Account.LastName}}, -An API key has been created for your account! +You have been granted access to the global configuration of your site. -To view your new API key, log in to your user account panel: -{{.AccountsAddress}} - -Your key will appear on the front page once logged in. - -Kind regards, -The ScienceMesh Team -` - -const accountAuthorizedTemplate = ` -Dear {{.Account.FirstName}} {{.Account.LastName}}, - -Congratulations - your site registration has been authorized! +Log in to your account to access this configuration: +{{.AccountsAddress}} Kind regards, The ScienceMesh Team @@ -61,7 +50,7 @@ Dear {{.Account.FirstName}} {{.Account.LastName}}, You have been granted access to the ScienceMesh GOCDB instance: {{.GOCDBAddress}} -Simply use your regular ScienceMesh account credentials to log in to the GOCDB. +Simply use your regular ScienceMesh Site Administrator Account credentials to log in to the GOCDB. Kind regards, The ScienceMesh Team @@ -89,9 +78,10 @@ const contactFormTemplate = ` ` const alertFiringNotificationTemplate = ` -Site {{.Params.Site}} has generated an alert: +Site '{{.Params.Site}}' has generated an alert: Type: {{.Params.Name}} + Service: {{.Params.Service}} Instance: {{.Params.Instance}} Job: {{.Params.Job}} Severity: {{.Params.Severity}} @@ -102,9 +92,10 @@ Site {{.Params.Site}} has generated an alert: ` const alertResolvedNotificationTemplate = ` -Site {{.Params.Site}} has resolved an alert: +Site '{{.Params.Site}}' has resolved an alert: Type: {{.Params.Name}} + Service: {{.Params.Service}} Instance: {{.Params.Instance}} Job: {{.Params.Job}} Severity: {{.Params.Severity}} diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go index 22dbdfc65a..0079843bc1 100644 --- a/pkg/siteacc/endpoints.go +++ b/pkg/siteacc/endpoints.go @@ -26,21 +26,20 @@ import ( "net/url" "strings" - "github.com/cs3org/reva/v2/pkg/mentix/key" - "github.com/cs3org/reva/v2/pkg/siteacc/config" - "github.com/cs3org/reva/v2/pkg/siteacc/data" - "github.com/cs3org/reva/v2/pkg/siteacc/html" - "github.com/cs3org/reva/v2/pkg/siteacc/manager" + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" + "github.com/cs3org/reva/pkg/siteacc/manager" "github.com/pkg/errors" "github.com/prometheus/alertmanager/template" ) const ( - invokerDefault = "" - invokerUser = "user" + invokerUser = "user" ) type methodCallback = func(*SiteAccounts, url.Values, []byte, *html.Session) (interface{}, error) +type accessSetterCallback = func(*manager.AccountsManager, *data.Account, bool) error type endpoint struct { Path string @@ -68,10 +67,6 @@ func getEndpoints() []endpoint { // Form/panel endpoints {config.EndpointAdministration, callAdministrationEndpoint, nil, false}, {config.EndpointAccount, callAccountEndpoint, nil, true}, - // API key endpoints - {config.EndpointGenerateAPIKey, callMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false}, - {config.EndpointVerifyAPIKey, callMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false}, - {config.EndpointAssignAPIKey, callMethodEndpoint, createMethodCallbacks(nil, handleAssignAPIKey), false}, // General account endpoints {config.EndpointList, callMethodEndpoint, createMethodCallbacks(handleList, nil), false}, {config.EndpointFind, callMethodEndpoint, createMethodCallbacks(handleFind, nil), false}, @@ -79,22 +74,21 @@ func getEndpoints() []endpoint { {config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false}, {config.EndpointConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleConfigure), false}, {config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false}, + // Site endpoints + {config.EndpointSiteGet, callMethodEndpoint, createMethodCallbacks(handleSiteGet, nil), false}, + {config.EndpointSiteConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSiteConfigure), false}, // Login endpoints {config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), true}, {config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), true}, - {config.EndpointResetPassword, callMethodEndpoint, createMethodCallbacks(nil, handleResetPassword), false}, - {config.EndpointContact, callMethodEndpoint, createMethodCallbacks(nil, handleContact), false}, + {config.EndpointResetPassword, callMethodEndpoint, createMethodCallbacks(nil, handleResetPassword), true}, + {config.EndpointContact, callMethodEndpoint, createMethodCallbacks(nil, handleContact), true}, // Authentication endpoints {config.EndpointVerifyUserToken, callMethodEndpoint, createMethodCallbacks(handleVerifyUserToken, nil), true}, - // Authorization endpoints - {config.EndpointAuthorize, callMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false}, - {config.EndpointIsAuthorized, callMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false}, // Access management endpoints + {config.EndpointGrantSiteAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantSiteAccess), false}, {config.EndpointGrantGOCDBAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantGOCDBAccess), false}, // Alerting endpoints {config.EndpointDispatchAlert, callMethodEndpoint, createMethodCallbacks(nil, handleDispatchAlert), false}, - // Account site endpoints - {config.EndpointUnregisterSite, callMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false}, } return endpoints @@ -160,63 +154,6 @@ func callMethodEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWrite _, _ = w.Write(jsonData) } -func handleGenerateAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - email := values.Get("email") - flags := key.FlagDefault - - if strings.EqualFold(values.Get("isScienceMesh"), "true") { - flags |= key.FlagScienceMesh - } - - if len(email) == 0 { - return nil, errors.Errorf("no email provided") - } - - apiKey, err := key.GenerateAPIKey(key.SaltFromEmail(email), flags) - if err != nil { - return nil, errors.Wrap(err, "unable to generate API key") - } - return map[string]string{"apiKey": apiKey}, nil -} - -func handleVerifyAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - apiKey := values.Get("apiKey") - email := values.Get("email") - - if len(apiKey) == 0 { - return nil, errors.Errorf("no API key provided") - } - - if len(email) == 0 { - return nil, errors.Errorf("no email provided") - } - - err := key.VerifyAPIKey(apiKey, key.SaltFromEmail(email)) - if err != nil { - return nil, errors.Wrap(err, "invalid API key") - } - return nil, nil -} - -func handleAssignAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - account, err := unmarshalRequestData(body) - if err != nil { - return nil, err - } - - flags := key.FlagDefault - if _, ok := values["isScienceMesh"]; ok { - flags |= key.FlagScienceMesh - } - - // Assign a new API key to the account through the accounts manager - if err := siteacc.AccountsManager().AssignAPIKeyToAccount(account, flags); err != nil { - return nil, errors.Wrap(err, "unable to assign API key") - } - - return nil, nil -} - func handleList(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { return siteacc.AccountsManager().CloneAccounts(true), nil } @@ -297,23 +234,37 @@ func handleRemove(siteacc *SiteAccounts, values url.Values, body []byte, session return nil, nil } -func handleIsAuthorized(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - account, err := findAccount(siteacc, values.Get("by"), values.Get("value")) - if err != nil { - return nil, err +func handleSiteGet(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + siteID := values.Get("site") + if siteID == "" { + return nil, errors.Errorf("no site specified") } - return account.Data.Authorized, nil + site := siteacc.SitesManager().FindSite(siteID) + if site == nil { + return nil, errors.Errorf("no site with ID %v exists", siteID) + } + return map[string]interface{}{"site": site.Clone(false)}, nil } -func handleUnregisterSite(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - account, err := unmarshalRequestData(body) +func handleSiteConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + email, _, err := processInvoker(siteacc, values, session) + if err != nil { + return nil, err + } + account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email) if err != nil { return nil, err } - // Unregister the account's site through the accounts manager - if err := siteacc.AccountsManager().UnregisterAccountSite(account); err != nil { - return nil, errors.Wrap(err, "unable to unregister the site of the given account") + siteData := &data.Site{} + if err := json.Unmarshal(body, siteData); err != nil { + return nil, errors.Wrap(err, "invalid form data") + } + siteData.ID = account.Site + + // Configure the site through the sites manager + if err := siteacc.SitesManager().UpdateSite(siteData); err != nil { + return nil, errors.Wrap(err, "unable to configure site") } return nil, nil @@ -355,7 +306,7 @@ func handleResetPassword(siteacc *SiteAccounts, values url.Values, body []byte, } func handleContact(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - if session.LoggedInUser == nil { + if !session.IsUserLoggedIn() { return nil, errors.Errorf("no user is currently logged in") } @@ -369,7 +320,7 @@ func handleContact(siteacc *SiteAccounts, values url.Values, body []byte, sessio } // Send an email through the accounts manager - siteacc.AccountsManager().SendContactForm(session.LoggedInUser, strings.TrimSpace(contactData.Subject), strings.TrimSpace(contactData.Message)) + siteacc.AccountsManager().SendContactForm(session.LoggedInUser().Account, strings.TrimSpace(contactData.Subject), strings.TrimSpace(contactData.Message)) return nil, nil } @@ -393,36 +344,6 @@ func handleVerifyUserToken(siteacc *SiteAccounts, values url.Values, body []byte return newToken, nil } -func handleAuthorize(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { - account, err := unmarshalRequestData(body) - if err != nil { - return nil, err - } - - if val := values.Get("status"); len(val) > 0 { - var authorize bool - switch strings.ToLower(val) { - case "true": - authorize = true - - case "false": - authorize = false - - default: - return nil, errors.Errorf("unsupported authorization status %v", val[0]) - } - - // Authorize the account through the accounts manager - if err := siteacc.AccountsManager().AuthorizeAccount(account, authorize); err != nil { - return nil, errors.Wrap(err, "unable to (un)authorize account") - } - } else { - return nil, errors.Errorf("no authorization status provided") - } - - return nil, nil -} - func handleDispatchAlert(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { alertsData := &template.Data{} if err := json.Unmarshal(body, alertsData); err != nil { @@ -437,7 +358,15 @@ func handleDispatchAlert(siteacc *SiteAccounts, values url.Values, body []byte, return nil, nil } +func handleGrantSiteAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + return handleGrantAccess((*manager.AccountsManager).GrantSiteAccess, siteacc, values, body, session) +} + func handleGrantGOCDBAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { + return handleGrantAccess((*manager.AccountsManager).GrantGOCDBAccess, siteacc, values, body, session) +} + +func handleGrantAccess(accessSetter accessSetterCallback, siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) { account, err := unmarshalRequestData(body) if err != nil { return nil, err @@ -457,8 +386,8 @@ func handleGrantGOCDBAccess(siteacc *SiteAccounts, values url.Values, body []byt } // Grant access to the account through the accounts manager - if err := siteacc.AccountsManager().GrantGOCDBAccess(account, grantAccess); err != nil { - return nil, errors.Wrap(err, "unable to change the GOCDB access status of the account") + if err := accessSetter(siteacc.AccountsManager(), account, grantAccess); err != nil { + return nil, errors.Wrap(err, "unable to change the access status of the account") } } else { return nil, errors.Errorf("no access status provided") @@ -494,27 +423,13 @@ func processInvoker(siteacc *SiteAccounts, values url.Values, session *html.Sess var invokedByUser bool switch strings.ToLower(values.Get("invoker")) { - case invokerDefault: - // If the endpoint was called through the API, an API key must be provided identifying the account - apiKey := values.Get("apikey") - if apiKey == "" { - return "", false, errors.Errorf("no API key provided") - } - - accountFound, err := findAccount(siteacc, manager.FindByAPIKey, apiKey) - if err != nil { - return "", false, errors.Wrap(err, "no account for the specified API key found") - } - email = accountFound.Email - invokedByUser = false - case invokerUser: // If this endpoint was called by the user, set the account email from the stored session - if session.LoggedInUser == nil { + if !session.IsUserLoggedIn() { return "", false, errors.Errorf("no user is currently logged in") } - email = session.LoggedInUser.Email + email = session.LoggedInUser().Account.Email invokedByUser = true default: diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go index 55bf3b4020..f12ea6b171 100644 --- a/pkg/siteacc/html/panel.go +++ b/pkg/siteacc/html/panel.go @@ -164,7 +164,7 @@ func (panel *Panel) getFullTemplateName(name string) string { func NewPanel(name string, provider PanelProvider, conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { panel := &Panel{} if err := panel.initialize(name, provider, conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the panel") + return nil, errors.Wrap(err, "unable to initialize the panel") } return panel, nil } diff --git a/pkg/siteacc/html/session.go b/pkg/siteacc/html/session.go index 852c524e73..54235febe2 100644 --- a/pkg/siteacc/html/session.go +++ b/pkg/siteacc/html/session.go @@ -37,16 +37,22 @@ type Session struct { CreationTime time.Time Timeout time.Duration - LoggedInUser *data.Account - Data map[string]interface{} + loggedInUser *SessionUser + expirationTime time.Time halflifeTime time.Time sessionCookieName string } +// SessionUser holds information about the logged in user +type SessionUser struct { + Account *data.Account + Site *data.Site +} + func getRemoteAddress(r *http.Request) string { // Remove the port number from the remote address remoteAddress := "" @@ -56,6 +62,29 @@ func getRemoteAddress(r *http.Request) string { return remoteAddress } +// LoggedInUser retrieves the currently logged in user or nil if none is logged in. +func (sess *Session) LoggedInUser() *SessionUser { + return sess.loggedInUser +} + +// LoginUser logs in the provided user. +func (sess *Session) LoginUser(acc *data.Account, site *data.Site) { + sess.loggedInUser = &SessionUser{ + Account: acc, + Site: site, + } +} + +// LogoutUser logs out the currently logged in user. +func (sess *Session) LogoutUser() { + sess.loggedInUser = nil +} + +// IsUserLoggedIn tells whether a user is currently logged in. +func (sess *Session) IsUserLoggedIn() bool { + return sess.loggedInUser != nil +} + // Save stores the session ID in a cookie using a response writer. func (sess *Session) Save(cookiePath string, w http.ResponseWriter) { fullURL, _ := url.Parse(cookiePath) @@ -108,6 +137,7 @@ func NewSession(name string, timeout time.Duration, r *http.Request) *Session { CreationTime: time.Now(), Timeout: timeout, Data: make(map[string]interface{}, 10), + loggedInUser: nil, expirationTime: time.Now().Add(timeout), halflifeTime: time.Now().Add(timeout / 2), sessionCookieName: name, diff --git a/pkg/siteacc/html/sessionmanager.go b/pkg/siteacc/html/sessionmanager.go index af6f396cfb..83b7f8aef3 100644 --- a/pkg/siteacc/html/sessionmanager.go +++ b/pkg/siteacc/html/sessionmanager.go @@ -157,9 +157,14 @@ func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (* // Carry over the old session information, thus preserving the existing session sessionNew.MigrationID = session.ID - sessionNew.LoggedInUser = session.LoggedInUser sessionNew.Data = session.Data + if user := session.LoggedInUser(); user != nil { + sessionNew.LoginUser(user.Account, user.Site) + } else { + sessionNew.LogoutUser() + } + // Delete the old session delete(mngr.sessions, session.ID) @@ -180,7 +185,7 @@ func (mngr *SessionManager) logSessionInfo(session *Session, r *http.Request, in func NewSessionManager(name string, conf *config.Configuration, log *zerolog.Logger) (*SessionManager, error) { mngr := &SessionManager{} if err := mngr.initialize(name, conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the session manager") + return nil, errors.Wrap(err, "unable to initialize the session manager") } return mngr, nil } diff --git a/pkg/siteacc/html/template.go b/pkg/siteacc/html/template.go index b0fa54706d..420dcd6f35 100644 --- a/pkg/siteacc/html/template.go +++ b/pkg/siteacc/html/template.go @@ -106,6 +106,9 @@ const panelTemplate = ` label { font-weight: bold; } + h1 { + text-align: center; + } .box { width: 100%; diff --git a/pkg/siteacc/manager/accmanager.go b/pkg/siteacc/manager/accmanager.go index 42e9d89e30..f4fc30b63b 100644 --- a/pkg/siteacc/manager/accmanager.go +++ b/pkg/siteacc/manager/accmanager.go @@ -19,18 +19,15 @@ package manager import ( - "path" "strings" "sync" "time" - "github.com/cs3org/reva/v2/pkg/mentix/key" - "github.com/cs3org/reva/v2/pkg/siteacc/config" - "github.com/cs3org/reva/v2/pkg/siteacc/data" - "github.com/cs3org/reva/v2/pkg/siteacc/email" - "github.com/cs3org/reva/v2/pkg/siteacc/manager/gocdb" - "github.com/cs3org/reva/v2/pkg/siteacc/sitereg" - "github.com/cs3org/reva/v2/pkg/smtpclient" + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/email" + "github.com/cs3org/reva/pkg/siteacc/manager/gocdb" + "github.com/cs3org/reva/pkg/smtpclient" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/sethvargo/go-password/password" @@ -39,10 +36,6 @@ import ( const ( // FindByEmail holds the string value of the corresponding search criterium. FindByEmail = "email" - // FindByAPIKey holds the string value of the corresponding search criterium. - FindByAPIKey = "apikey" - // FindBySiteID holds the string value of the corresponding search criterium. - FindBySiteID = "siteid" ) // AccountsManager is responsible for all site account related tasks. @@ -50,16 +43,17 @@ type AccountsManager struct { conf *config.Configuration log *zerolog.Logger + storage data.Storage + accounts data.Accounts accountsListeners []AccountsListener - storage data.Storage smtp *smtpclient.SMTPCredentials mutex sync.RWMutex } -func (mngr *AccountsManager) initialize(conf *config.Configuration, log *zerolog.Logger) error { +func (mngr *AccountsManager) initialize(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) error { if conf == nil { return errors.Errorf("no configuration provided") } @@ -70,15 +64,13 @@ func (mngr *AccountsManager) initialize(conf *config.Configuration, log *zerolog } mngr.log = log - mngr.accounts = make(data.Accounts, 0, 32) // Reserve some space for accounts - - // Create the site accounts storage and read all stored data - if storage, err := mngr.createStorage(conf.Storage.Driver); err == nil { - mngr.storage = storage - mngr.readAllAccounts() - } else { - return errors.Wrap(err, "unable to create accounts storage") + if storage == nil { + return errors.Errorf("no storage provided") } + mngr.storage = storage + + mngr.accounts = make(data.Accounts, 0, 32) // Reserve some space for accounts + mngr.readAllAccounts() // Register accounts listeners if listener, err := gocdb.NewListener(mngr.conf, mngr.log); err == nil { @@ -95,16 +87,8 @@ func (mngr *AccountsManager) initialize(conf *config.Configuration, log *zerolog return nil } -func (mngr *AccountsManager) createStorage(driver string) (data.Storage, error) { - if driver == "file" { - return data.NewFileStorage(mngr.conf, mngr.log) - } - - return nil, errors.Errorf("unknown storage driver %v", driver) -} - func (mngr *AccountsManager) readAllAccounts() { - if accounts, err := mngr.storage.ReadAll(); err == nil { + if accounts, err := mngr.storage.ReadAccounts(); err == nil { mngr.accounts = *accounts } else { // Just warn when not being able to read accounts @@ -113,7 +97,7 @@ func (mngr *AccountsManager) readAllAccounts() { } func (mngr *AccountsManager) writeAllAccounts() { - if err := mngr.storage.WriteAll(&mngr.accounts); err != nil { + if err := mngr.storage.WriteAccounts(&mngr.accounts); err != nil { // Just warn when not being able to write accounts mngr.log.Warn().Err(err).Msg("error while writing accounts") } @@ -129,12 +113,6 @@ func (mngr *AccountsManager) findAccount(by string, value string) (*data.Account case FindByEmail: account = mngr.findAccountByPredicate(func(account *data.Account) bool { return strings.EqualFold(account.Email, value) }) - case FindByAPIKey: - account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.Data.APIKey == value }) - - case FindBySiteID: - account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.GetSiteID() == value }) - default: return nil, errors.Errorf("invalid search type %v", by) } @@ -266,8 +244,8 @@ func (mngr *AccountsManager) FindAccountEx(by string, value string, cloneAccount return account, nil } -// AuthorizeAccount sets the authorization status of the account identified by the account email; if no such account exists, an error is returned. -func (mngr *AccountsManager) AuthorizeAccount(accountData *data.Account, authorized bool) error { +// GrantSiteAccess sets the Site access status of the account identified by the account email; if no such account exists, an error is returned. +func (mngr *AccountsManager) GrantSiteAccess(accountData *data.Account, grantAccess bool) error { mngr.mutex.Lock() defer mngr.mutex.Unlock() @@ -276,19 +254,7 @@ func (mngr *AccountsManager) AuthorizeAccount(accountData *data.Account, authori return errors.Wrap(err, "no account with the specified email exists") } - authorizedOld := account.Data.Authorized - account.Data.Authorized = authorized - - mngr.storage.AccountUpdated(account) - mngr.writeAllAccounts() - - if account.Data.Authorized && account.Data.Authorized != authorizedOld { - mngr.sendEmail(account, nil, email.SendAccountAuthorized) - } - - mngr.callListeners(account, AccountsListener.AccountUpdated) - - return nil + return mngr.grantAccess(account, &account.Data.SiteAccess, grantAccess, email.SendSiteAccessGranted) } // GrantGOCDBAccess sets the GOCDB access status of the account identified by the account email; if no such account exists, an error is returned. @@ -301,80 +267,7 @@ func (mngr *AccountsManager) GrantGOCDBAccess(accountData *data.Account, grantAc return errors.Wrap(err, "no account with the specified email exists") } - accessOld := account.Data.GOCDBAccess - account.Data.GOCDBAccess = grantAccess - - mngr.storage.AccountUpdated(account) - mngr.writeAllAccounts() - - if account.Data.GOCDBAccess && account.Data.GOCDBAccess != accessOld { - mngr.sendEmail(account, nil, email.SendGOCDBAccessGranted) - } - - mngr.callListeners(account, AccountsListener.AccountUpdated) - - return nil -} - -// AssignAPIKeyToAccount is used to assign a new API key to the account identified by the account email; if no such account exists, an error is returned. -func (mngr *AccountsManager) AssignAPIKeyToAccount(accountData *data.Account, flags int) error { - mngr.mutex.Lock() - defer mngr.mutex.Unlock() - - account, err := mngr.findAccount(FindByEmail, accountData.Email) - if err != nil { - return errors.Wrap(err, "no account with the specified email exists") - } - - if len(account.Data.APIKey) > 0 { - return errors.Errorf("the account already has an API key assigned") - } - - for { - apiKey, err := key.GenerateAPIKey(key.SaltFromEmail(account.Email), flags) - if err != nil { - return errors.Wrap(err, "error while generating API key") - } - - // See if the key already exists (super extremely unlikely); if so, generate a new one and try again - if acc, _ := mngr.findAccount(FindByAPIKey, apiKey); acc != nil { - continue - } - - account.Data.APIKey = apiKey - break - } - - mngr.storage.AccountUpdated(account) - mngr.writeAllAccounts() - - mngr.sendEmail(account, nil, email.SendAPIKeyAssigned) - mngr.callListeners(account, AccountsListener.AccountUpdated) - - return nil -} - -// UnregisterAccountSite unregisters the site associated with the given account. -func (mngr *AccountsManager) UnregisterAccountSite(accountData *data.Account) error { - mngr.mutex.RLock() - defer mngr.mutex.RUnlock() - - account, err := mngr.findAccount(FindByEmail, accountData.Email) - if err != nil { - return errors.Wrap(err, "no account with the specified email exists") - } - - salt := key.SaltFromEmail(account.Email) - siteID, err := key.CalculateSiteID(account.Data.APIKey, salt) - if err != nil { - return errors.Wrap(err, "unable to get site ID") - } - - if err := sitereg.UnregisterSite(path.Join(mngr.conf.Mentix.URL, mngr.conf.Mentix.SiteRegistrationEndpoint), account.Data.APIKey, siteID, salt); err != nil { - return errors.Wrap(err, "error while unregistering the site") - } - - return nil + return mngr.grantAccess(account, &account.Data.GOCDBAccess, grantAccess, email.SendGOCDBAccessGranted) } // RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned. @@ -414,6 +307,22 @@ func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts { return clones } +func (mngr *AccountsManager) grantAccess(account *data.Account, accessFlag *bool, grantAccess bool, emailFunc email.SendFunction) error { + accessOld := *accessFlag + *accessFlag = grantAccess + + mngr.storage.AccountUpdated(account) + mngr.writeAllAccounts() + + if *accessFlag && *accessFlag != accessOld { + mngr.sendEmail(account, nil, emailFunc) + } + + mngr.callListeners(account, AccountsListener.AccountUpdated) + + return nil +} + func (mngr *AccountsManager) callListeners(account *data.Account, cb AccountsListenerCallback) { for _, listener := range mngr.accountsListeners { cb(listener, account) @@ -425,10 +334,10 @@ func (mngr *AccountsManager) sendEmail(account *data.Account, params map[string] } // NewAccountsManager creates a new accounts manager instance. -func NewAccountsManager(conf *config.Configuration, log *zerolog.Logger) (*AccountsManager, error) { +func NewAccountsManager(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) (*AccountsManager, error) { mngr := &AccountsManager{} - if err := mngr.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the accounts manager") + if err := mngr.initialize(storage, conf, log); err != nil { + return nil, errors.Wrap(err, "unable to initialize the accounts manager") } return mngr, nil } diff --git a/pkg/siteacc/manager/gocdb/gocdb.go b/pkg/siteacc/manager/gocdb/gocdb.go index fb7d315435..fbb2d99e2b 100644 --- a/pkg/siteacc/manager/gocdb/gocdb.go +++ b/pkg/siteacc/manager/gocdb/gocdb.go @@ -75,7 +75,7 @@ func (listener *AccountsListener) updateGOCDB(account *data.Account, forceRemova func NewListener(conf *config.Configuration, log *zerolog.Logger) (*AccountsListener, error) { listener := &AccountsListener{} if err := listener.initialize(conf, log); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the GOCDB accounts listener") + return nil, errors.Wrap(err, "unable to initialize the GOCDB accounts listener") } return listener, nil } diff --git a/pkg/siteacc/manager/sitesmanager.go b/pkg/siteacc/manager/sitesmanager.go new file mode 100644 index 0000000000..0d5a4eeb8f --- /dev/null +++ b/pkg/siteacc/manager/sitesmanager.go @@ -0,0 +1,185 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package manager + +import ( + "strings" + "sync" + + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +// SitesManager is responsible for all sites related tasks. +type SitesManager struct { + conf *config.Configuration + log *zerolog.Logger + + storage data.Storage + + sites data.Sites + + mutex sync.RWMutex +} + +func (mngr *SitesManager) initialize(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) error { + if conf == nil { + return errors.Errorf("no configuration provided") + } + mngr.conf = conf + + if log == nil { + return errors.Errorf("no logger provided") + } + mngr.log = log + + if storage == nil { + return errors.Errorf("no storage provided") + } + mngr.storage = storage + + mngr.sites = make(data.Sites, 0, 32) // Reserve some space for sites + mngr.readAllSites() + + return nil +} + +func (mngr *SitesManager) readAllSites() { + if sites, err := mngr.storage.ReadSites(); err == nil { + mngr.sites = *sites + } else { + // Just warn when not being able to read sites + mngr.log.Warn().Err(err).Msg("error while reading sites") + } +} + +func (mngr *SitesManager) writeAllSites() { + if err := mngr.storage.WriteSites(&mngr.sites); err != nil { + // Just warn when not being able to write sites + mngr.log.Warn().Err(err).Msg("error while writing sites") + } +} + +// GetSite retrieves the site with the given ID, creating it first if necessary. +func (mngr *SitesManager) GetSite(id string, cloneSite bool) (*data.Site, error) { + mngr.mutex.RLock() + defer mngr.mutex.RUnlock() + + site, err := mngr.getSite(id) + if err != nil { + return nil, err + } + + if cloneSite { + site = site.Clone(false) + } + + return site, nil +} + +// FindSite returns the site specified by the ID if one exists. +func (mngr *SitesManager) FindSite(id string) *data.Site { + site, _ := mngr.findSite(id) + return site +} + +// UpdateSite updates the site identified by the site ID; if no such site exists, one will be created first. +func (mngr *SitesManager) UpdateSite(siteData *data.Site) error { + mngr.mutex.Lock() + defer mngr.mutex.Unlock() + + site, err := mngr.getSite(siteData.ID) + if err != nil { + return errors.Wrap(err, "site to update not found") + } + + if err := site.Update(siteData, mngr.conf.Security.CredentialsPassphrase); err == nil { + mngr.storage.SiteUpdated(site) + mngr.writeAllSites() + } else { + return errors.Wrap(err, "error while updating site") + } + + return nil +} + +// CloneSites retrieves all sites currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible. +func (mngr *SitesManager) CloneSites(eraseCredentials bool) data.Sites { + mngr.mutex.RLock() + defer mngr.mutex.RUnlock() + + clones := make(data.Sites, 0, len(mngr.sites)) + for _, site := range mngr.sites { + clones = append(clones, site.Clone(eraseCredentials)) + } + + return clones +} + +func (mngr *SitesManager) getSite(id string) (*data.Site, error) { + site, err := mngr.findSite(id) + if site == nil { + site, err = mngr.createSite(id) + } + return site, err +} + +func (mngr *SitesManager) createSite(id string) (*data.Site, error) { + site, err := data.NewSite(id) + if err != nil { + return nil, errors.Wrap(err, "error while creating site") + } + mngr.sites = append(mngr.sites, site) + mngr.storage.SiteAdded(site) + mngr.writeAllSites() + return site, nil +} + +func (mngr *SitesManager) findSite(id string) (*data.Site, error) { + if len(id) == 0 { + return nil, errors.Errorf("no search ID specified") + } + + site := mngr.findSiteByPredicate(func(site *data.Site) bool { return strings.EqualFold(site.ID, id) }) + if site != nil { + return site, nil + } + + return nil, errors.Errorf("no site found matching the specified ID") +} + +func (mngr *SitesManager) findSiteByPredicate(predicate func(*data.Site) bool) *data.Site { + for _, site := range mngr.sites { + if predicate(site) { + return site + } + } + return nil +} + +// NewSitesManager creates a new sites manager instance. +func NewSitesManager(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) (*SitesManager, error) { + mngr := &SitesManager{} + if err := mngr.initialize(storage, conf, log); err != nil { + return nil, errors.Wrap(err, "unable to initialize the sites manager") + } + return mngr, nil +} diff --git a/pkg/siteacc/manager/usersmanager.go b/pkg/siteacc/manager/usersmanager.go index b68e16995f..280a373837 100644 --- a/pkg/siteacc/manager/usersmanager.go +++ b/pkg/siteacc/manager/usersmanager.go @@ -32,6 +32,7 @@ type UsersManager struct { conf *config.Configuration log *zerolog.Logger + sitesManager *SitesManager accountsManager *AccountsManager } @@ -39,7 +40,7 @@ const ( defaultPasswordLength = 12 ) -func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Logger, accountsManager *AccountsManager) error { +func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Logger, sitesManager *SitesManager, accountsManager *AccountsManager) error { if conf == nil { return errors.Errorf("no configuration provided") } @@ -50,6 +51,11 @@ func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Lo } mngr.log = log + if sitesManager == nil { + return errors.Errorf("no sites manager provided") + } + mngr.sitesManager = sitesManager + if accountsManager == nil { return errors.Errorf("no accounts manager provided") } @@ -75,11 +81,17 @@ func (mngr *UsersManager) LoginUser(name, password string, scope string, session return "", errors.Errorf("no access to the specified scope granted") } + // Get the site the account belongs to + site, err := mngr.sitesManager.GetSite(account.Site, false) + if err != nil { + return "", errors.Wrap(err, "no site with the specified ID exists") + } + // Store the user account in the session - session.LoggedInUser = account + session.LoginUser(account, site) // Generate a token that can be used as a "ticket" - token, err := generateUserToken(session.LoggedInUser.Email, scope, mngr.conf.Webserver.SessionTimeout) + token, err := generateUserToken(session.LoggedInUser().Account.Email, scope, mngr.conf.Webserver.SessionTimeout) if err != nil { return "", errors.Wrap(err, "unable to generate user token") } @@ -90,7 +102,7 @@ func (mngr *UsersManager) LoginUser(name, password string, scope string, session // LogoutUser logs the current user out. func (mngr *UsersManager) LogoutUser(session *html.Session) { // Just unset the user account stored in the session - session.LoggedInUser = nil + session.LogoutUser() } // VerifyUserToken is used to verify a user token against the current session. @@ -129,10 +141,10 @@ func (mngr *UsersManager) VerifyUserToken(token string, user string, scope strin } // NewUsersManager creates a new users manager instance. -func NewUsersManager(conf *config.Configuration, log *zerolog.Logger, accountsManager *AccountsManager) (*UsersManager, error) { +func NewUsersManager(conf *config.Configuration, log *zerolog.Logger, sitesManager *SitesManager, accountsManager *AccountsManager) (*UsersManager, error) { mngr := &UsersManager{} - if err := mngr.initialize(conf, log, accountsManager); err != nil { - return nil, errors.Wrapf(err, "unable to initialize the users manager") + if err := mngr.initialize(conf, log, sitesManager, accountsManager); err != nil { + return nil, errors.Wrap(err, "unable to initialize the users manager") } return mngr, nil } diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go index abeb6ef4e3..ffa7bff7e6 100644 --- a/pkg/siteacc/siteacc.go +++ b/pkg/siteacc/siteacc.go @@ -22,12 +22,13 @@ import ( "fmt" "net/http" - accpanel "github.com/cs3org/reva/v2/pkg/siteacc/account" - "github.com/cs3org/reva/v2/pkg/siteacc/admin" - "github.com/cs3org/reva/v2/pkg/siteacc/alerting" - "github.com/cs3org/reva/v2/pkg/siteacc/config" - "github.com/cs3org/reva/v2/pkg/siteacc/html" - "github.com/cs3org/reva/v2/pkg/siteacc/manager" + accpanel "github.com/cs3org/reva/pkg/siteacc/account" + "github.com/cs3org/reva/pkg/siteacc/admin" + "github.com/cs3org/reva/pkg/siteacc/alerting" + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" + "github.com/cs3org/reva/pkg/siteacc/manager" "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -39,6 +40,9 @@ type SiteAccounts struct { sessions *html.SessionManager + storage data.Storage + + sitesManager *manager.SitesManager accountsManager *manager.AccountsManager usersManager *manager.UsersManager @@ -66,15 +70,29 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog } siteacc.sessions = sessions + // Create the central storage + storage, err := siteacc.createStorage(conf.Storage.Driver) + if err != nil { + return errors.Wrap(err, "unable to create storage") + } + siteacc.storage = storage + + // Create the sites manager instance + smngr, err := manager.NewSitesManager(storage, conf, log) + if err != nil { + return errors.Wrap(err, "error creating the sites manager") + } + siteacc.sitesManager = smngr + // Create the accounts manager instance - amngr, err := manager.NewAccountsManager(conf, log) + amngr, err := manager.NewAccountsManager(storage, conf, log) if err != nil { return errors.Wrap(err, "error creating the accounts manager") } siteacc.accountsManager = amngr // Create the users manager instance - umngr, err := manager.NewUsersManager(conf, log, siteacc.accountsManager) + umngr, err := manager.NewUsersManager(conf, log, siteacc.sitesManager, siteacc.accountsManager) if err != nil { return errors.Wrap(err, "error creating the users manager") } @@ -144,6 +162,11 @@ func (siteacc *SiteAccounts) ShowAccountPanel(w http.ResponseWriter, r *http.Req return siteacc.accountPanel.Execute(w, r, session) } +// SitesManager returns the central sites manager instance. +func (siteacc *SiteAccounts) SitesManager() *manager.SitesManager { + return siteacc.sitesManager +} + // AccountsManager returns the central accounts manager instance. func (siteacc *SiteAccounts) AccountsManager() *manager.AccountsManager { return siteacc.accountsManager @@ -173,6 +196,14 @@ func (siteacc *SiteAccounts) GetPublicEndpoints() []string { return endpoints } +func (siteacc *SiteAccounts) createStorage(driver string) (data.Storage, error) { + if driver == "file" { + return data.NewFileStorage(siteacc.conf, siteacc.log) + } + + return nil, errors.Errorf("unknown storage driver %v", driver) +} + // New returns a new Site Accounts service instance. func New(conf *config.Configuration, log *zerolog.Logger) (*SiteAccounts, error) { // Configure the accounts service diff --git a/pkg/storage/favorite/loader/loader.go b/pkg/storage/favorite/loader/loader.go index 424b8fc337..7ebcb6cbd2 100644 --- a/pkg/storage/favorite/loader/loader.go +++ b/pkg/storage/favorite/loader/loader.go @@ -19,7 +19,7 @@ package loader import ( - // Load share cache drivers. - _ "github.com/cs3org/reva/v2/pkg/storage/favorite/memory" + // Load storage favorite drivers. + _ "github.com/cs3org/reva/pkg/storage/favorite/memory" // Add your own here ) diff --git a/pkg/storage/fs/cephfs/cephfs.go b/pkg/storage/fs/cephfs/cephfs.go index 0d907fb8f9..e71db2df75 100644 --- a/pkg/storage/fs/cephfs/cephfs.go +++ b/pkg/storage/fs/cephfs/cephfs.go @@ -33,10 +33,10 @@ import ( cephfs2 "github.com/ceph/go-ceph/cephfs" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/storage" - "github.com/cs3org/reva/v2/pkg/storage/fs/registry" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) diff --git a/pkg/storage/fs/cephfs/connections.go b/pkg/storage/fs/cephfs/connections.go index 7819894f15..7b928eaa63 100644 --- a/pkg/storage/fs/cephfs/connections.go +++ b/pkg/storage/fs/cephfs/connections.go @@ -31,7 +31,7 @@ import ( grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/pkg/errors" cephfs2 "github.com/ceph/go-ceph/cephfs" diff --git a/pkg/storage/fs/cephfs/errors.go b/pkg/storage/fs/cephfs/errors.go index 2c4564613c..a4ab013c97 100644 --- a/pkg/storage/fs/cephfs/errors.go +++ b/pkg/storage/fs/cephfs/errors.go @@ -30,7 +30,7 @@ import "C" import ( "fmt" - "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/pkg/errtypes" ) func wrapErrorMsg(code C.int) string { diff --git a/pkg/storage/fs/cephfs/options.go b/pkg/storage/fs/cephfs/options.go index cd949d36fb..0b4b81f0e6 100644 --- a/pkg/storage/fs/cephfs/options.go +++ b/pkg/storage/fs/cephfs/options.go @@ -24,7 +24,7 @@ package cephfs import ( "path/filepath" - "github.com/cs3org/reva/v2/pkg/sharedconf" + "github.com/cs3org/reva/pkg/sharedconf" ) // Options for the cephfs module diff --git a/pkg/storage/fs/cephfs/unsupported.go b/pkg/storage/fs/cephfs/unsupported.go index a409351b14..a337f3f789 100644 --- a/pkg/storage/fs/cephfs/unsupported.go +++ b/pkg/storage/fs/cephfs/unsupported.go @@ -24,8 +24,8 @@ package cephfs import ( "github.com/pkg/errors" - "github.com/cs3org/reva/v2/pkg/storage" - "github.com/cs3org/reva/v2/pkg/storage/fs/registry" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" ) func init() { diff --git a/pkg/storage/fs/cephfs/upload.go b/pkg/storage/fs/cephfs/upload.go index 87ff95f2b2..2d8eccf7c9 100644 --- a/pkg/storage/fs/cephfs/upload.go +++ b/pkg/storage/fs/cephfs/upload.go @@ -32,10 +32,10 @@ import ( cephfs2 "github.com/ceph/go-ceph/cephfs" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - ctx2 "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/appctx" + ctx2 "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" tusd "github.com/tus/tusd/pkg/handler" diff --git a/pkg/storage/fs/cephfs/user.go b/pkg/storage/fs/cephfs/user.go index 29e94860b1..e99686adfd 100644 --- a/pkg/storage/fs/cephfs/user.go +++ b/pkg/storage/fs/cephfs/user.go @@ -29,15 +29,15 @@ import ( "strings" "syscall" - "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/pkg/errtypes" cephfs2 "github.com/ceph/go-ceph/cephfs" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - ctx2 "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/mime" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + ctx2 "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/mime" + "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/pkg/errors" ) diff --git a/pkg/storage/fs/loader/loader.go b/pkg/storage/fs/loader/loader.go index d79e58eb3c..cd88c5ddc7 100644 --- a/pkg/storage/fs/loader/loader.go +++ b/pkg/storage/fs/loader/loader.go @@ -20,17 +20,18 @@ package loader import ( // Load core storage filesystem backends. - _ "github.com/cs3org/reva/v2/pkg/storage/fs/cephfs" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/eos" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpc" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpchome" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/eoshome" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/local" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/localhome" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/ocis" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/s3" - _ "github.com/cs3org/reva/v2/pkg/storage/fs/s3ng" + _ "github.com/cs3org/reva/pkg/storage/fs/cephfs" + _ "github.com/cs3org/reva/pkg/storage/fs/eos" + _ "github.com/cs3org/reva/pkg/storage/fs/eosgrpc" + _ "github.com/cs3org/reva/pkg/storage/fs/eosgrpchome" + _ "github.com/cs3org/reva/pkg/storage/fs/eoshome" + _ "github.com/cs3org/reva/pkg/storage/fs/local" + _ "github.com/cs3org/reva/pkg/storage/fs/localhome" + _ "github.com/cs3org/reva/pkg/storage/fs/nextcloud" + _ "github.com/cs3org/reva/pkg/storage/fs/ocis" + _ "github.com/cs3org/reva/pkg/storage/fs/owncloud" + _ "github.com/cs3org/reva/pkg/storage/fs/owncloudsql" + _ "github.com/cs3org/reva/pkg/storage/fs/s3" + _ "github.com/cs3org/reva/pkg/storage/fs/s3ng" // Add your own here ) diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index ed6adeea23..d5c42a5b48 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -790,7 +790,7 @@ func (nc *StorageDriver) RefreshLock(ctx context.Context, ref *provider.Referenc } // Unlock removes an existing lock from the given reference -func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { +func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go index 74cb43d2a2..db514cbb5c 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go +++ b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go @@ -66,7 +66,6 @@ var responses = map[string]Response{ `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome `: {200, ``, serverStateHome}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome {}`: {200, ``, serverStateHome}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal","name":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"}`: {200, `{"status":{"code":1}}`, serverStateHome}, - `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal"}`: {200, `{"status":{"code":1}}`, serverStateHome}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateReference {"path":"/Shares/reference","url":"scheme://target"}`: {200, `[]`, serverStateReference}, @@ -104,8 +103,7 @@ var responses = map[string]Response{ `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/GetPathByID {"storage_id":"00000000-0000-0000-0000-000000000000","opaque_id":"fileid-/some/path"} EMPTY`: {200, "/subdir", serverStateEmpty}, - `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"path":"/file"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty}, - `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"resource_id":{"storage_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"path":"/versionedFile"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty}, + `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"path":"/file"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/ListFolder {"ref":{"path":"/"},"mdKeys":null}`: {200, `[{"opaque":{},"type":2,"id":{"opaque_id":"fileid-/subdir"},"checksum":{},"etag":"deadbeef","mime_type":"text/plain","mtime":{"seconds":1234567890},"path":"/subdir","permission_set":{},"size":12345,"canonical_metadata":{},"owner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"arbitrary_metadata":{"metadata":{"da":"ta","some":"arbi","trary":"meta"}}}]`, serverStateEmpty}, diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index 893459f954..c0936e3c1c 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -32,10 +32,10 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud" - jwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/storage/fs/nextcloud" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go new file mode 100644 index 0000000000..c75f82fde1 --- /dev/null +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -0,0 +1,2368 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package owncloud + +import ( + "context" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/logger" + "github.com/cs3org/reva/pkg/mime" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" + "github.com/cs3org/reva/pkg/storage/utils/ace" + "github.com/cs3org/reva/pkg/storage/utils/chunking" + "github.com/cs3org/reva/pkg/storage/utils/templates" + "github.com/gomodule/redigo/redis" + "github.com/google/uuid" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "github.com/pkg/xattr" +) + +const ( + // Currently,extended file attributes have four separated + // namespaces (user, trusted, security and system) followed by a dot. + // A non root user can only manipulate the user. namespace, which is what + // we will use to store ownCloud specific metadata. To prevent name + // collisions with other apps We are going to introduce a sub namespace + // "user.oc." + ocPrefix string = "user.oc." + + // idAttribute is the name of the filesystem extended attribute that is used to store the uuid in + idAttribute string = ocPrefix + "id" + + // SharePrefix is the prefix for sharing related extended attributes + sharePrefix string = ocPrefix + "grant." // grants are similar to acls, but they are not propagated down the tree when being changed + trashOriginPrefix string = ocPrefix + "o" + mdPrefix string = ocPrefix + "md." // arbitrary metadata + favPrefix string = ocPrefix + "fav." // favorite flag, per user + etagPrefix string = ocPrefix + "etag." // allow overriding a calculated etag with one from the extended attributes + checksumPrefix string = ocPrefix + "cs." + checksumsKey string = "http://owncloud.org/ns/checksums" + favoriteKey string = "http://owncloud.org/ns/favorite" +) + +var defaultPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ + // no permissions +} +var ownerPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ + // all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, +} + +func init() { + registry.Register("owncloud", New) +} + +type config struct { + DataDirectory string `mapstructure:"datadirectory"` + UploadInfoDir string `mapstructure:"upload_info_dir"` + DeprecatedShareDirectory string `mapstructure:"sharedirectory"` + ShareFolder string `mapstructure:"share_folder"` + UserLayout string `mapstructure:"user_layout"` + Redis string `mapstructure:"redis"` + EnableHome bool `mapstructure:"enable_home"` + Scan bool `mapstructure:"scan"` + UserProviderEndpoint string `mapstructure:"userprovidersvc"` +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} + +func (c *config) init(m map[string]interface{}) { + if c.Redis == "" { + c.Redis = ":6379" + } + if c.UserLayout == "" { + c.UserLayout = "{{.Id.OpaqueId}}" + } + if c.UploadInfoDir == "" { + c.UploadInfoDir = "/var/tmp/reva/uploadinfo" + } + // fallback for old config + if c.DeprecatedShareDirectory != "" { + c.ShareFolder = c.DeprecatedShareDirectory + } + if c.ShareFolder == "" { + c.ShareFolder = "/Shares" + } + // ensure share folder always starts with slash + c.ShareFolder = filepath.Join("/", c.ShareFolder) + + // default to scanning if not configured + if _, ok := m["scan"]; !ok { + c.Scan = true + } + c.UserProviderEndpoint = sharedconf.GetGatewaySVC(c.UserProviderEndpoint) +} + +// New returns an implementation to of the storage.FS interface that talk to +// a local filesystem. +func New(m map[string]interface{}) (storage.FS, error) { + c, err := parseConfig(m) + if err != nil { + return nil, err + } + c.init(m) + + // c.DataDirectory should never end in / unless it is the root? + c.DataDirectory = filepath.Clean(c.DataDirectory) + + // create datadir if it does not exist + err = os.MkdirAll(c.DataDirectory, 0700) + if err != nil { + logger.New().Error().Err(err). + Str("path", c.DataDirectory). + Msg("could not create datadir") + } + + err = os.MkdirAll(c.UploadInfoDir, 0700) + if err != nil { + logger.New().Error().Err(err). + Str("path", c.UploadInfoDir). + Msg("could not create uploadinfo dir") + } + + pool := &redis.Pool{ + + MaxIdle: 3, + IdleTimeout: 240 * time.Second, + + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", c.Redis) + if err != nil { + return nil, err + } + return c, err + }, + + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + + return &ocfs{ + c: c, + pool: pool, + chunkHandler: chunking.NewChunkHandler(c.UploadInfoDir), + }, nil +} + +type ocfs struct { + c *config + pool *redis.Pool + chunkHandler *chunking.ChunkHandler +} + +func (fs *ocfs) Shutdown(ctx context.Context) error { + return fs.pool.Close() +} + +// scan files and add uuid to path mapping to kv store +func (fs *ocfs) scanFiles(ctx context.Context, conn redis.Conn) { + if fs.c.Scan { + fs.c.Scan = false // TODO ... in progress use mutex ? + log := appctx.GetLogger(ctx) + log.Debug().Str("path", fs.c.DataDirectory).Msg("scanning data directory") + err := filepath.Walk(fs.c.DataDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Error().Str("path", path).Err(err).Msg("error accessing path") + return filepath.SkipDir + } + // TODO(jfd) skip versions folder only if direct in users home dir + // we need to skip versions, otherwise a lookup by id might resolve to a version + if strings.Contains(path, "files_versions") { + log.Debug().Str("path", path).Err(err).Msg("skipping versions") + return filepath.SkipDir + } + + // reuse connection to store file ids + id := readOrCreateID(context.Background(), path, nil) + _, err = conn.Do("SET", id, path) + if err != nil { + log.Error().Str("path", path).Err(err).Msg("error caching id") + // continue scanning + return nil + } + + log.Debug().Str("path", path).Str("id", id).Msg("scanned path") + return nil + }) + if err != nil { + log.Error().Err(err).Str("path", fs.c.DataDirectory).Msg("error scanning data directory") + } + } +} + +// owncloud stores files in the files subfolder +// the incoming path starts with /, so we need to insert the files subfolder into the path +// and prefix the data directory +// TODO the path handed to a storage provider should not contain the username +func (fs *ocfs) toInternalPath(ctx context.Context, sp string) (ip string) { + if fs.c.EnableHome { + u := ctxpkg.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + // The inner filepath.Join prevents the path from breaking out of + // //files/ + ip = filepath.Join(fs.c.DataDirectory, layout, "files", filepath.Join("/", sp)) + } else { + // trim all / + sp = strings.Trim(sp, "/") + // p = "" or + // p = or + // p = /foo/bar.txt + segments := strings.SplitN(sp, "/", 2) + + if len(segments) == 1 && segments[0] == "" { + ip = fs.c.DataDirectory + return + } + + // parts[0] contains the username or userid. + u, err := fs.getUser(ctx, segments[0]) + if err != nil { + // TODO return invalid internal path? + return + } + layout := templates.WithUser(u, fs.c.UserLayout) + + if len(segments) == 1 { + // parts = "" + ip = filepath.Join(fs.c.DataDirectory, layout, "files") + } else { + // parts = "", "foo/bar.txt" + ip = filepath.Join(fs.c.DataDirectory, layout, "files", filepath.Join(segments[1])) + } + + } + return +} + +func (fs *ocfs) toInternalShadowPath(ctx context.Context, sp string) (internal string) { + if fs.c.EnableHome { + u := ctxpkg.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files", sp) + } else { + // trim all / + sp = strings.Trim(sp, "/") + // p = "" or + // p = or + // p = /foo/bar.txt + segments := strings.SplitN(sp, "/", 2) + + if len(segments) == 1 && segments[0] == "" { + internal = fs.c.DataDirectory + return + } + + // parts[0] contains the username or userid. + u, err := fs.getUser(ctx, segments[0]) + if err != nil { + // TODO return invalid internal path? + return + } + layout := templates.WithUser(u, fs.c.UserLayout) + + if len(segments) == 1 { + // parts = "" + internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files") + } else { + // parts = "", "foo/bar.txt" + internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files", segments[1]) + } + } + return +} + +// ownloud stores versions in the files_versions subfolder +// the incoming path starts with /, so we need to insert the files subfolder into the path +// and prefix the data directory +// TODO the path handed to a storage provider should not contain the username +func (fs *ocfs) getVersionsPath(ctx context.Context, ip string) string { + // ip = /path/to/data//files/foo/bar.txt + // remove data dir + if fs.c.DataDirectory != "/" { + // fs.c.DataDirectory is a clean path, so it never ends in / + ip = strings.TrimPrefix(ip, fs.c.DataDirectory) + } + // ip = //files/foo/bar.txt + parts := strings.SplitN(ip, "/", 4) + + // parts[1] contains the username or userid. + u, err := fs.getUser(ctx, parts[1]) + if err != nil { + // TODO return invalid internal path? + return "" + } + layout := templates.WithUser(u, fs.c.UserLayout) + + switch len(parts) { + case 3: + // parts = "", "" + return filepath.Join(fs.c.DataDirectory, layout, "files_versions") + case 4: + // parts = "", "", "foo/bar.txt" + return filepath.Join(fs.c.DataDirectory, layout, "files_versions", filepath.Join("/", parts[3])) + default: + return "" // TODO Must not happen? + } + +} + +// owncloud stores trashed items in the files_trashbin subfolder of a users home +func (fs *ocfs) getRecyclePath(ctx context.Context) (string, error) { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") + return "", err + } + layout := templates.WithUser(u, fs.c.UserLayout) + return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/files"), nil +} + +func (fs *ocfs) getVersionRecyclePath(ctx context.Context) (string, error) { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") + return "", err + } + layout := templates.WithUser(u, fs.c.UserLayout) + return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/files_versions"), nil +} + +func (fs *ocfs) toStoragePath(ctx context.Context, ip string) (sp string) { + if fs.c.EnableHome { + u := ctxpkg.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + trim := filepath.Join(fs.c.DataDirectory, layout, "files") + sp = strings.TrimPrefix(ip, trim) + // root directory + if sp == "" { + sp = "/" + } + } else { + // ip = /data//files/foo/bar.txt + // remove data dir + if fs.c.DataDirectory != "/" { + // fs.c.DataDirectory is a clean path, so it never ends in / + ip = strings.TrimPrefix(ip, fs.c.DataDirectory) + // ip = //files/foo/bar.txt + } + + segments := strings.SplitN(ip, "/", 4) + // parts = "", "", "files", "foo/bar.txt" + switch len(segments) { + case 1: + sp = "/" + case 2: + sp = filepath.Join("/", segments[1]) + case 3: + sp = filepath.Join("/", segments[1]) + default: + sp = filepath.Join("/", segments[1], segments[3]) + } + } + log := appctx.GetLogger(ctx) + log.Debug().Str("driver", "ocfs").Str("ipath", ip).Str("spath", sp).Msg("toStoragePath") + return +} + +func (fs *ocfs) toStorageShadowPath(ctx context.Context, ip string) (sp string) { + if fs.c.EnableHome { + u := ctxpkg.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + trim := filepath.Join(fs.c.DataDirectory, layout, "shadow_files") + sp = strings.TrimPrefix(ip, trim) + } else { + // ip = /data//shadow_files/foo/bar.txt + // remove data dir + if fs.c.DataDirectory != "/" { + // fs.c.DataDirectory is a clean path, so it never ends in / + ip = strings.TrimPrefix(ip, fs.c.DataDirectory) + // ip = //shadow_files/foo/bar.txt + } + + segments := strings.SplitN(ip, "/", 4) + // parts = "", "", "shadow_files", "foo/bar.txt" + switch len(segments) { + case 1: + sp = "/" + case 2: + sp = filepath.Join("/", segments[1]) + case 3: + sp = filepath.Join("/", segments[1]) + default: + sp = filepath.Join("/", segments[1], segments[3]) + } + } + appctx.GetLogger(ctx).Debug().Str("driver", "ocfs").Str("ipath", ip).Str("spath", sp).Msg("toStorageShadowPath") + return +} + +// TODO the owner needs to come from a different place +func (fs *ocfs) getOwner(ip string) string { + ip = strings.TrimPrefix(ip, fs.c.DataDirectory) + parts := strings.SplitN(ip, "/", 3) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// TODO cache user lookup +func (fs *ocfs) getUser(ctx context.Context, usernameOrID string) (id *userpb.User, err error) { + u := ctxpkg.ContextMustGetUser(ctx) + // check if username matches and id is set + if u.Username == usernameOrID && u.Id != nil && u.Id.OpaqueId != "" { + return u, nil + } + // check if userid matches and username is set + if u.Id != nil && u.Id.OpaqueId == usernameOrID && u.Username != "" { + return u, nil + } + // look up at the userprovider + + // parts[0] contains the username or userid. use user service to look up id + c, err := pool.GetUserProviderServiceClient(fs.c.UserProviderEndpoint) + if err != nil { + appctx.GetLogger(ctx). + Error().Err(err). + Str("userprovidersvc", fs.c.UserProviderEndpoint). + Str("usernameOrID", usernameOrID). + Msg("could not get user provider client") + return nil, err + } + res, err := c.GetUser(ctx, &userpb.GetUserRequest{ + UserId: &userpb.UserId{OpaqueId: usernameOrID}, + }) + if err != nil { + appctx.GetLogger(ctx). + Error().Err(err). + Str("userprovidersvc", fs.c.UserProviderEndpoint). + Str("usernameOrID", usernameOrID). + Msg("could not get user") + return nil, err + } + + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + appctx.GetLogger(ctx). + Error(). + Str("userprovidersvc", fs.c.UserProviderEndpoint). + Str("usernameOrID", usernameOrID). + Interface("status", res.Status). + Msg("user not found") + return nil, fmt.Errorf("user not found") + } + + if res.Status.Code != rpc.Code_CODE_OK { + appctx.GetLogger(ctx). + Error(). + Str("userprovidersvc", fs.c.UserProviderEndpoint). + Str("usernameOrID", usernameOrID). + Interface("status", res.Status). + Msg("user lookup failed") + return nil, fmt.Errorf("user lookup failed") + } + return res.User, nil +} + +// permissionSet returns the permission set for the current user +func (fs *ocfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { + if owner == nil { + return &provider.ResourcePermissions{ + Stat: true, + } + } + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id == nil { + return &provider.ResourcePermissions{ + // no permissions + } + } + if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { + return &provider.ResourcePermissions{ + // owner has all permissions + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } + } + // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it + return &provider.ResourcePermissions{ + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListFileVersions: true, + ListGrants: true, + ListRecycle: true, + Move: true, + PurgeRecycle: true, + RemoveGrant: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + } +} +func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip string, sp string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { + id := readOrCreateID(ctx, ip, c) + + etag := calcEtag(ctx, fi) + + if val, err := xattr.Get(ip, etagPrefix+etag); err == nil { + appctx.GetLogger(ctx).Debug(). + Str("ipath", ip). + Str("calcetag", etag). + Str("etag", string(val)). + Msg("overriding calculated etag") + etag = string(val) + } + + mdKeysMap := make(map[string]struct{}) + for _, k := range mdKeys { + mdKeysMap[k] = struct{}{} + } + + var returnAllKeys bool + if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok { + returnAllKeys = true + } + + metadata := map[string]string{} + + if _, ok := mdKeysMap[favoriteKey]; returnAllKeys || ok { + favorite := "" + if u, ok := ctxpkg.ContextGetUser(ctx); ok { + // the favorite flag is specific to the user, so we need to incorporate the userid + if uid := u.GetId(); uid != nil { + fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) + if val, err := xattr.Get(ip, fa); err == nil { + appctx.GetLogger(ctx).Debug(). + Str("ipath", ip). + Str("favorite", string(val)). + Str("username", u.GetUsername()). + Msg("found favorite flag") + favorite = string(val) + } + } else { + appctx.GetLogger(ctx).Error().Err(errtypes.UserRequired("userrequired")).Msg("user has no id") + } + } else { + appctx.GetLogger(ctx).Error().Err(errtypes.UserRequired("userrequired")).Msg("error getting user from ctx") + } + metadata[favoriteKey] = favorite + } + + list, err := xattr.List(ip) + if err == nil { + for _, entry := range list { + // filter out non-custom properties + if !strings.HasPrefix(entry, mdPrefix) { + continue + } + if val, err := xattr.Get(ip, entry); err == nil { + k := entry[len(mdPrefix):] + if _, ok := mdKeysMap[k]; returnAllKeys || ok { + metadata[k] = string(val) + } + } else { + appctx.GetLogger(ctx).Error().Err(err). + Str("entry", entry). + Msg("error retrieving xattr metadata") + } + } + } else { + appctx.GetLogger(ctx).Error().Err(err).Msg("error getting list of extended attributes") + } + + ri := &provider.ResourceInfo{ + Id: &provider.ResourceId{OpaqueId: id}, + Path: sp, + Type: getResourceType(fi.IsDir()), + Etag: etag, + MimeType: mime.Detect(fi.IsDir(), ip), + Size: uint64(fi.Size()), + Mtime: &types.Timestamp{ + Seconds: uint64(fi.ModTime().Unix()), + // TODO read nanos from where? Nanos: fi.MTimeNanos, + }, + ArbitraryMetadata: &provider.ArbitraryMetadata{ + Metadata: metadata, + }, + } + + if owner, err := fs.getUser(ctx, fs.getOwner(ip)); err == nil { + ri.Owner = owner.Id + } else { + appctx.GetLogger(ctx).Error().Err(err).Msg("error getting owner") + } + + ri.PermissionSet = fs.permissionSet(ctx, ri.Owner) + + // checksums + if !fi.IsDir() { + if _, checksumRequested := mdKeysMap[checksumsKey]; returnAllKeys || checksumRequested { + // TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1? + readChecksumIntoResourceChecksum(ctx, ip, storageprovider.XSSHA1, ri) + readChecksumIntoOpaque(ctx, ip, storageprovider.XSMD5, ri) + readChecksumIntoOpaque(ctx, ip, storageprovider.XSAdler32, ri) + } + } + + return ri +} +func getResourceType(isDir bool) provider.ResourceType { + if isDir { + return provider.ResourceType_RESOURCE_TYPE_CONTAINER + } + return provider.ResourceType_RESOURCE_TYPE_FILE +} + +// CreateStorageSpace creates a storage space +func (fs *ocfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { + return nil, fmt.Errorf("unimplemented: CreateStorageSpace") +} + +func readOrCreateID(ctx context.Context, ip string, conn redis.Conn) string { + log := appctx.GetLogger(ctx) + + // read extended file attribute for id + // generate if not present + var id []byte + var err error + if id, err = xattr.Get(ip, idAttribute); err != nil { + log.Warn().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error reading file id") + + uuid := uuid.New() + // store uuid + id = uuid[:] + if err := xattr.Set(ip, idAttribute, id); err != nil { + log.Error().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error storing file id") + } + // TODO cache path for uuid in redis + // TODO reuse conn? + if conn != nil { + _, err := conn.Do("SET", uuid.String(), ip) + if err != nil { + log.Error().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error caching id") + // continue + } + } + } + // todo sign metadata + var uid uuid.UUID + if uid, err = uuid.FromBytes(id); err != nil { + log.Error().Err(err).Msg("error parsing uuid") + return "" + } + return uid.String() +} + +func (fs *ocfs) getPath(ctx context.Context, id *provider.ResourceId) (string, error) { + log := appctx.GetLogger(ctx) + c := fs.pool.Get() + defer c.Close() + fs.scanFiles(ctx, c) + ip, err := redis.String(c.Do("GET", id.OpaqueId)) + if err != nil { + return "", errtypes.NotFound(id.OpaqueId) + } + + idFromXattr, err := xattr.Get(ip, idAttribute) + if err != nil { + return "", errtypes.NotFound(id.OpaqueId) + } + + uid, err := uuid.FromBytes(idFromXattr) + if err != nil { + log.Error().Err(err).Msg("error parsing uuid") + } + + if uid.String() != id.OpaqueId { + if _, err := c.Do("DEL", id.OpaqueId); err != nil { + return "", err + } + return "", errtypes.NotFound(id.OpaqueId) + } + + return ip, nil +} + +// GetPathByID returns the storage relative path for the file id, without the internal namespace +func (fs *ocfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { + ip, err := fs.getPath(ctx, id) + if err != nil { + return "", err + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.GetPath { + return "", errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return "", errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return "", errors.Wrap(err, "ocfs: error reading permissions") + } + + return fs.toStoragePath(ctx, ip), nil +} + +// resolve takes in a request path or request id and converts it to an internal path. +func (fs *ocfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { + + // if storage id is set look up that + if ref.ResourceId != nil { + ip, err := fs.getPath(ctx, ref.ResourceId) + if err != nil { + return "", err + } + return filepath.Join("/", ip, filepath.Join("/", ref.Path)), nil + } + + // use a path + return fs.toInternalPath(ctx, ref.Path), nil + +} + +func (fs *ocfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { + return errtypes.NotSupported("ocfs: deny grant not supported") +} + +func (fs *ocfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { + ip, err := fs.resolve(ctx, ref) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.AddGrant { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + e := ace.FromGrant(g) + principal, value := e.Marshal() + if err := xattr.Set(ip, sharePrefix+principal, value); err != nil { + return err + } + return fs.propagate(ctx, ip) +} + +// extractACEsFromAttrs reads ACEs in the list of attrs from the file +func extractACEsFromAttrs(ctx context.Context, ip string, attrs []string) (entries []*ace.ACE) { + log := appctx.GetLogger(ctx) + entries = []*ace.ACE{} + for i := range attrs { + if strings.HasPrefix(attrs[i], sharePrefix) { + var value []byte + var err error + if value, err = xattr.Get(ip, attrs[i]); err != nil { + log.Error().Err(err).Str("attr", attrs[i]).Msg("could not read attribute") + continue + } + var e *ace.ACE + principal := attrs[i][len(sharePrefix):] + if e, err = ace.Unmarshal(principal, value); err != nil { + log.Error().Err(err).Str("principal", principal).Str("attr", attrs[i]).Msg("could not unmarshal ace") + continue + } + entries = append(entries, e) + } + } + return +} + +// TODO if user is owner but no acls found he can do everything? +// The owncloud driver does not integrate with the os so, for now, the owner can do everything, see ownerPermissions. +// Should this change we can store an acl for the owner in every node. +// We could also add default acls that can only the admin can set, eg for a read only storage? +// Someone needs to write to provide the content that should be read only, so this would likely be an acl for a group anyway. +// We need the storage relative path so we can calculate the permissions +// for the node based on all acls in the tree up to the root +func (fs *ocfs) readPermissions(ctx context.Context, ip string) (p *provider.ResourcePermissions, err error) { + + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("no user in context, returning default permissions") + return defaultPermissions, nil + } + // check if the current user is the owner + if fs.getOwner(ip) == u.Id.OpaqueId { + appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("user is owner, returning owner permissions") + return ownerPermissions, nil + } + + // for non owners this is a little more complicated: + aggregatedPermissions := &provider.ResourcePermissions{} + // add default permissions + addPermissions(aggregatedPermissions, defaultPermissions) + + // determine root + rp := fs.toInternalPath(ctx, "") + // TODO rp will be the datadir ... be we don't want to go up that high. The users home is far enough + np := ip + + // for an efficient group lookup convert the list of groups to a map + // groups are just strings ... groupnames ... or group ids ??? AAARGH !!! + groupsMap := make(map[string]bool, len(u.Groups)) + for i := range u.Groups { + groupsMap[u.Groups[i]] = true + } + + var e *ace.ACE + // for all segments, starting at the leaf + for np != rp { + + var attrs []string + if attrs, err = xattr.List(np); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("ipath", np).Msg("error listing attributes") + return nil, err + } + + userace := sharePrefix + "u:" + u.Id.OpaqueId + userFound := false + for i := range attrs { + // we only need the find the user once per node + switch { + case !userFound && attrs[i] == userace: + e, err = fs.readACE(ctx, np, "u:"+u.Id.OpaqueId) + case strings.HasPrefix(attrs[i], sharePrefix+"g:"): + g := strings.TrimPrefix(attrs[i], sharePrefix+"g:") + if groupsMap[g] { + e, err = fs.readACE(ctx, np, "g:"+g) + } else { + // no need to check attribute + continue + } + default: + // no need to check attribute + continue + } + + switch { + case err == nil: + addPermissions(aggregatedPermissions, e.Grant().GetPermissions()) + appctx.GetLogger(ctx).Debug().Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Interface("permissions", aggregatedPermissions).Msg("adding permissions") + case isNoData(err): + err = nil + appctx.GetLogger(ctx).Error().Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Interface("attrs", attrs).Msg("no permissions found on node, but they were listed") + default: + appctx.GetLogger(ctx).Error().Err(err).Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Msg("error reading permissions") + return nil, err + } + } + + np = filepath.Dir(np) + } + + // 3. read user permissions until one is found? + // what if, when checking /a/b/c/d, /a/b has write permission, but /a/b/c has not? + // those are two shares one read only, and a higher one rw, + // should the higher one be used? + // or, since we did find a matching ace in a lower node use that because it matches the principal? + // this would allow ai user to share a folder rm but take away the write capability for eg a docs folder inside it. + // 4. read group permissions until all groups of the user are matched? + // same as for user permission, but we need to keep going further up the tree until all groups of the user were matched. + // what if a user has thousands of groups? + // we will always have to walk to the root. + // but the same problem occurs for a user with 2 groups but where only one group was used to share. + // in any case we need to iterate the aces, not the number of groups of the user. + // listing the aces can be used to match the principals, we do not need to fully real all aces + // what if, when checking /a/b/c/d, /a/b has write permission for group g, but /a/b/c has an ace for another group h the user is also a member of? + // it would allow restricting a users permissions by resharing something with him with lower permission? + // so if you have reshare permissions you could accidentially restrict users access to a subfolder of a rw share to ro by sharing it to another group as ro when they are part of both groups + // it makes more sense to have explicit negative permissions + + // TODO we need to read all parents ... until we find a matching ace? + appctx.GetLogger(ctx).Debug().Interface("permissions", aggregatedPermissions).Str("ipath", ip).Msg("returning aggregated permissions") + return aggregatedPermissions, nil +} + +func isNoData(err error) bool { + if xerr, ok := err.(*xattr.Error); ok { + if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { + return serr == syscall.ENODATA + } + } + return false +} + +// The os not exists error is buried inside the xattr error, +// so we cannot just use os.IsNotExists(). +func isNotFound(err error) bool { + if xerr, ok := err.(*xattr.Error); ok { + if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { + return serr == syscall.ENOENT + } + } + return false +} + +func (fs *ocfs) readACE(ctx context.Context, ip string, principal string) (e *ace.ACE, err error) { + var b []byte + if b, err = xattr.Get(ip, sharePrefix+principal); err != nil { + return nil, err + } + if e, err = ace.Unmarshal(principal, b); err != nil { + return nil, err + } + return +} + +// additive merging of permissions only +func addPermissions(p1 *provider.ResourcePermissions, p2 *provider.ResourcePermissions) { + p1.AddGrant = p1.AddGrant || p2.AddGrant + p1.CreateContainer = p1.CreateContainer || p2.CreateContainer + p1.Delete = p1.Delete || p2.Delete + p1.GetPath = p1.GetPath || p2.GetPath + p1.GetQuota = p1.GetQuota || p2.GetQuota + p1.InitiateFileDownload = p1.InitiateFileDownload || p2.InitiateFileDownload + p1.InitiateFileUpload = p1.InitiateFileUpload || p2.InitiateFileUpload + p1.ListContainer = p1.ListContainer || p2.ListContainer + p1.ListFileVersions = p1.ListFileVersions || p2.ListFileVersions + p1.ListGrants = p1.ListGrants || p2.ListGrants + p1.ListRecycle = p1.ListRecycle || p2.ListRecycle + p1.Move = p1.Move || p2.Move + p1.PurgeRecycle = p1.PurgeRecycle || p2.PurgeRecycle + p1.RemoveGrant = p1.RemoveGrant || p2.RemoveGrant + p1.RestoreFileVersion = p1.RestoreFileVersion || p2.RestoreFileVersion + p1.RestoreRecycleItem = p1.RestoreRecycleItem || p2.RestoreRecycleItem + p1.Stat = p1.Stat || p2.Stat + p1.UpdateGrant = p1.UpdateGrant || p2.UpdateGrant +} + +func (fs *ocfs) ListGrants(ctx context.Context, ref *provider.Reference) (grants []*provider.Grant, err error) { + log := appctx.GetLogger(ctx) + var ip string + if ip, err = fs.resolve(ctx, ref); err != nil { + return nil, errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListGrants { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + var attrs []string + if attrs, err = xattr.List(ip); err != nil { + // TODO err might be a not exists + log.Error().Err(err).Msg("error listing attributes") + return nil, err + } + + log.Debug().Interface("attrs", attrs).Msg("read attributes") + + aces := extractACEsFromAttrs(ctx, ip, attrs) + + grants = make([]*provider.Grant, 0, len(aces)) + for i := range aces { + grants = append(grants, aces[i].Grant()) + } + + return grants, nil +} + +func (fs *ocfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { + + var ip string + if ip, err = fs.resolve(ctx, ref); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListContainer { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + var attr string + if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + attr = sharePrefix + "g:" + g.Grantee.GetGroupId().OpaqueId + } else { + attr = sharePrefix + "u:" + g.Grantee.GetUserId().OpaqueId + } + + if err = xattr.Remove(ip, attr); err != nil { + return + } + + return fs.propagate(ctx, ip) +} + +func (fs *ocfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { + ip, err := fs.resolve(ctx, ref) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.UpdateGrant { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + e := ace.FromGrant(g) + principal, value := e.Marshal() + if err := xattr.Set(ip, sharePrefix+principal, value); err != nil { + return err + } + return fs.propagate(ctx, ip) +} + +func (fs *ocfs) CreateHome(ctx context.Context) error { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") + return err + } + layout := templates.WithUser(u, fs.c.UserLayout) + + homePaths := []string{ + filepath.Join(fs.c.DataDirectory, layout, "files"), + filepath.Join(fs.c.DataDirectory, layout, "files_trashbin"), + filepath.Join(fs.c.DataDirectory, layout, "files_versions"), + filepath.Join(fs.c.DataDirectory, layout, "uploads"), + filepath.Join(fs.c.DataDirectory, layout, "shadow_files"), + } + + for _, v := range homePaths { + if err := os.MkdirAll(v, 0700); err != nil { + return errors.Wrap(err, "ocfs: error creating home path: "+v) + } + } + + return nil +} + +// If home is enabled, the relative home is always the empty string +func (fs *ocfs) GetHome(ctx context.Context) (string, error) { + if !fs.c.EnableHome { + return "", errtypes.NotSupported("ocfs: get home not supported") + } + return "", nil +} + +func (fs *ocfs) CreateDir(ctx context.Context, ref *provider.Reference) (err error) { + + ip, err := fs.resolve(ctx, ref) + if err != nil { + return err + } + + // check permissions of parent dir + if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil { + if !perm.CreateContainer { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(ref.Path) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + if err = os.Mkdir(ip, 0700); err != nil { + if os.IsNotExist(err) { + return errtypes.NotFound(ref.Path) + } + // FIXME we also need already exists error, webdav expects 405 MethodNotAllowed + return errors.Wrap(err, "ocfs: error creating dir "+ref.Path) + } + return fs.propagate(ctx, ip) +} + +// TouchFile as defined in the storage.FS interface +func (fs *ocfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + +func (fs *ocfs) isShareFolderChild(sp string) bool { + return strings.HasPrefix(sp, fs.c.ShareFolder) +} + +func (fs *ocfs) isShareFolderRoot(sp string) bool { + return sp == fs.c.ShareFolder +} + +func (fs *ocfs) CreateReference(ctx context.Context, sp string, targetURI *url.URL) error { + if !fs.isShareFolderChild(sp) { + return errtypes.PermissionDenied("ocfs: cannot create references outside the share folder: share_folder=" + "/Shares" + " path=" + sp) + } + + ip := fs.toInternalShadowPath(ctx, sp) + // TODO check permission? + + dir, _ := filepath.Split(ip) + if err := os.MkdirAll(dir, 0700); err != nil { + return errors.Wrapf(err, "ocfs: error creating shadow path %s", dir) + } + + f, err := os.Create(ip) + if err != nil { + return errors.Wrapf(err, "ocfs: error creating shadow file %s", ip) + } + + err = xattr.FSet(f, mdPrefix+"target", []byte(targetURI.String())) + if err != nil { + return errors.Wrapf(err, "ocfs: error setting the target %s on the shadow file %s", targetURI.String(), ip) + } + return nil +} + +func (fs *ocfs) setMtime(ctx context.Context, ip string, mtime string) error { + log := appctx.GetLogger(ctx) + if mt, err := parseMTime(mtime); err == nil { + // updating mtime also updates atime + if err := os.Chtimes(ip, mt, mt); err != nil { + log.Error().Err(err). + Str("ipath", ip). + Time("mtime", mt). + Msg("could not set mtime") + return errors.Wrap(err, "could not set mtime") + } + } else { + log.Error().Err(err). + Str("ipath", ip). + Str("mtime", mtime). + Msg("could not parse mtime") + return errors.Wrap(err, "could not parse mtime") + } + return nil +} +func (fs *ocfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) { + log := appctx.GetLogger(ctx) + + var ip string + if ip, err = fs.resolve(ctx, ref); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.InitiateFileUpload { // TODO add dedicated permission? + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + var fi os.FileInfo + fi, err = os.Stat(ip) + if err != nil { + if os.IsNotExist(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error stating "+ip) + } + + errs := []error{} + + if md.Metadata != nil { + if val, ok := md.Metadata["mtime"]; ok { + err := fs.setMtime(ctx, ip, val) + if err != nil { + errs = append(errs, errors.Wrap(err, "could not set mtime")) + } + // remove from metadata + delete(md.Metadata, "mtime") + } + // TODO(jfd) special handling for atime? + // TODO(jfd) allow setting birth time (btime)? + // TODO(jfd) any other metadata that is interesting? fileid? + if val, ok := md.Metadata["etag"]; ok { + etag := calcEtag(ctx, fi) + val = fmt.Sprintf("\"%s\"", strings.Trim(val, "\"")) + if etag == val { + log.Debug(). + Str("ipath", ip). + Str("etag", val). + Msg("ignoring request to update identical etag") + } else + // etag is only valid until the calculated etag changes + // TODO(jfd) cleanup in a batch job + if err := xattr.Set(ip, etagPrefix+etag, []byte(val)); err != nil { + log.Error().Err(err). + Str("ipath", ip). + Str("calcetag", etag). + Str("etag", val). + Msg("could not set etag") + errs = append(errs, errors.Wrap(err, "could not set etag")) + } + delete(md.Metadata, "etag") + } + if val, ok := md.Metadata["http://owncloud.org/ns/favorite"]; ok { + // TODO we should not mess with the user here ... the favorites is now a user specific property for a file + // that cannot be mapped to extended attributes without leaking who has marked a file as a favorite + // it is a specific case of a tag, which is user individual as well + // TODO there are different types of tags + // 1. public that are managed by everyone + // 2. private tags that are only visible to the user + // 3. system tags that are only visible to the system + // 4. group tags that are only visible to a group ... + // urgh ... well this can be solved using different namespaces + // 1. public = p: + // 2. private = u:: for user specific + // 3. system = s: for system + // 4. group = g:: + // 5. app? = a:: for apps? + // obviously this only is secure when the u/s/g/a namespaces are not accessible by users in the filesystem + // public tags can be mapped to extended attributes + if u, ok := ctxpkg.ContextGetUser(ctx); ok { + // the favorite flag is specific to the user, so we need to incorporate the userid + if uid := u.GetId(); uid != nil { + fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) + if err := xattr.Set(ip, fa, []byte(val)); err != nil { + log.Error().Err(err). + Str("ipath", ip). + Interface("user", u). + Str("key", fa). + Msg("could not set favorite flag") + errs = append(errs, errors.Wrap(err, "could not set favorite flag")) + } + } else { + log.Error(). + Str("ipath", ip). + Interface("user", u). + Msg("user has no id") + errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) + } + } else { + log.Error(). + Str("ipath", ip). + Interface("user", u). + Msg("error getting user from ctx") + errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) + } + // remove from metadata + delete(md.Metadata, "http://owncloud.org/ns/favorite") + } + } + for k, v := range md.Metadata { + if err := xattr.Set(ip, mdPrefix+k, []byte(v)); err != nil { + log.Error().Err(err). + Str("ipath", ip). + Str("key", k). + Str("val", v). + Msg("could not set metadata") + errs = append(errs, errors.Wrap(err, "could not set metadata")) + } + } + switch len(errs) { + case 0: + return fs.propagate(ctx, ip) + case 1: + return errs[0] + default: + // TODO how to return multiple errors? + return errors.New("multiple errors occurred, see log for details") + } +} + +func parseMTime(v string) (t time.Time, err error) { + p := strings.SplitN(v, ".", 2) + var sec, nsec int64 + if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil { + if len(p) > 1 { + nsec, err = strconv.ParseInt(p[1], 10, 64) + } + } + return time.Unix(sec, nsec), err +} + +func (fs *ocfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) { + log := appctx.GetLogger(ctx) + + var ip string + if ip, err = fs.resolve(ctx, ref); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.InitiateFileUpload { // TODO add dedicated permission? + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + _, err = os.Stat(ip) + if err != nil { + if os.IsNotExist(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error stating "+ip) + } + + errs := []error{} + for _, k := range keys { + switch k { + case "http://owncloud.org/ns/favorite": + if u, ok := ctxpkg.ContextGetUser(ctx); ok { + // the favorite flag is specific to the user, so we need to incorporate the userid + if uid := u.GetId(); uid != nil { + fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) + if err := xattr.Remove(ip, fa); err != nil { + log.Error().Err(err). + Str("ipath", ip). + Interface("user", u). + Str("key", fa). + Msg("could not unset favorite flag") + errs = append(errs, errors.Wrap(err, "could not unset favorite flag")) + } + } else { + log.Error(). + Str("ipath", ip). + Interface("user", u). + Msg("user has no id") + errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) + } + } else { + log.Error(). + Str("ipath", ip). + Interface("user", u). + Msg("error getting user from ctx") + errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) + } + default: + if err = xattr.Remove(ip, mdPrefix+k); err != nil { + // a non-existing attribute will return an error, which we can ignore + // (using string compare because the error type is syscall.Errno and not wrapped/recognizable) + if e, ok := err.(*xattr.Error); !ok || !(e.Err.Error() == "no data available" || + // darwin + e.Err.Error() == "attribute not found") { + log.Error().Err(err). + Str("ipath", ip). + Str("key", k). + Msg("could not unset metadata") + errs = append(errs, errors.Wrap(err, "could not unset metadata")) + } + } + } + } + + switch len(errs) { + case 0: + return fs.propagate(ctx, ip) + case 1: + return errs[0] + default: + // TODO how to return multiple errors? + return errors.New("multiple errors occurred, see log for details") + } +} + +// GetLock returns an existing lock on the given reference +func (fs *ocfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// SetLock puts a lock on the given reference +func (fs *ocfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { + return errtypes.NotSupported("unimplemented") +} + +// RefreshLock refreshes an existing lock on the given reference +func (fs *ocfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { + return errtypes.NotSupported("unimplemented") +} + +// Unlock removes an existing lock from the given reference +func (fs *ocfs) Unlock(ctx context.Context, ref *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// Delete is actually only a move to trash +// +// This is a first optimistic approach. +// When a file has versions and we want to delete the file it could happen that +// the service crashes before all moves are finished. +// That would result in invalid state like the main files was moved but the +// versions were not. +// We will live with that compromise since this storage driver will be +// deprecated soon. +func (fs *ocfs) Delete(ctx context.Context, ref *provider.Reference) (err error) { + var ip string + if ip, err = fs.resolve(ctx, ref); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.Delete { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + _, err = os.Stat(ip) + if err != nil { + if os.IsNotExist(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return errors.Wrap(err, "ocfs: error stating "+ip) + } + + rp, err := fs.getRecyclePath(ctx) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving recycle path") + } + + if err := os.MkdirAll(rp, 0700); err != nil { + return errors.Wrap(err, "ocfs: error creating trashbin dir "+rp) + } + + // ip is the path on disk ... we need only the path relative to root + origin := filepath.Dir(fs.toStoragePath(ctx, ip)) + + err = fs.trash(ctx, ip, rp, origin) + if err != nil { + return errors.Wrapf(err, "ocfs: error deleting file %s", ip) + } + err = fs.trashVersions(ctx, ip, origin) + if err != nil { + return errors.Wrapf(err, "ocfs: error deleting versions of file %s", ip) + } + return nil +} + +func (fs *ocfs) trash(ctx context.Context, ip string, rp string, origin string) error { + // set origin location in metadata + if err := xattr.Set(ip, trashOriginPrefix, []byte(origin)); err != nil { + return err + } + + // move to trash location + dtime := time.Now().Unix() + tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) + + // The condition reads: "if the file exists" + // I know this check is hard to read because of the double negation + // but this way we avoid to duplicate the code following the if block. + // If two deletes happen fast consecutively they will have the same `dtime`, + // therefore we have to increase the 'dtime' to avoid collisions. + if _, err := os.Stat(tgt); !errors.Is(err, os.ErrNotExist) { + // timestamp collision, try again with higher value: + dtime++ + tgt = filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) + } + if err := os.Rename(ip, tgt); err != nil { + return errors.Wrap(err, "ocfs: could not move item to trash") + } + + return fs.propagate(ctx, filepath.Dir(ip)) +} + +func (fs *ocfs) trashVersions(ctx context.Context, ip string, origin string) error { + vp := fs.getVersionsPath(ctx, ip) + vrp, err := fs.getVersionRecyclePath(ctx) + if err != nil { + return errors.Wrap(err, "error resolving versions recycle path") + } + + if err := os.MkdirAll(vrp, 0700); err != nil { + return errors.Wrap(err, "ocfs: error creating trashbin dir "+vrp) + } + + // Ignore error since the only possible error is malformed pattern. + versions, _ := filepath.Glob(vp + ".v*") + for _, v := range versions { + err := fs.trash(ctx, v, vrp, origin) + if err != nil { + return errors.Wrap(err, "ocfs: error deleting file "+v) + } + } + return nil +} + +func (fs *ocfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) (err error) { + var oldIP string + if oldIP, err = fs.resolve(ctx, oldRef); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, oldIP); err == nil { + if !perm.Move { // TODO add dedicated permission? + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(oldIP))) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + var newIP string + if newIP, err = fs.resolve(ctx, newRef); err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // TODO check target permissions ... if it exists + + if err = os.Rename(oldIP, newIP); err != nil { + return errors.Wrap(err, "ocfs: error moving "+oldIP+" to "+newIP) + } + + log := appctx.GetLogger(ctx) + conn := fs.pool.Get() + defer conn.Close() + // Ideally if we encounter an error here we should rollback the Move/Rename. + // But since the owncloud storage driver is not being actively used by anyone other + // than the acceptance tests we should be fine by ignoring the errors. + _ = filepath.Walk(newIP, func(path string, info os.FileInfo, err error) error { + if err != nil { + // TODO(c0rby): rollback the move in case of an error + log.Error().Str("path", path).Err(err).Msg("error caching id") + return nil + } + id := readOrCreateID(context.Background(), path, nil) + _, err = conn.Do("SET", id, path) + if err != nil { + // TODO(c0rby): rollback the move in case of an error + log.Error().Str("path", path).Err(err).Msg("error caching id") + } + return nil + }) + if err := fs.propagate(ctx, newIP); err != nil { + return err + } + if err := fs.propagate(ctx, filepath.Dir(oldIP)); err != nil { + return err + } + return nil +} + +func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) { + ip, err := fs.resolve(ctx, ref) + if err != nil { + // TODO return correct errtype + if _, ok := err.(errtypes.IsNotFound); ok { + return nil, err + } + return nil, errors.Wrap(err, "ocfs: error resolving reference") + } + p := fs.toStoragePath(ctx, ip) + + if fs.c.EnableHome { + if fs.isShareFolderChild(p) { + return fs.getMDShareFolder(ctx, p, mdKeys) + } + } + + // If GetMD is called for a path shared with the user then the path is + // already wrapped. (fs.resolve wraps the path) + if strings.HasPrefix(p, fs.c.DataDirectory) { + ip = p + } + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.Stat { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + md, err := os.Stat(ip) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return nil, errors.Wrap(err, "ocfs: error stating "+ip) + } + c := fs.pool.Get() + defer c.Close() + m := fs.convertToResourceInfo(ctx, md, ip, fs.toStoragePath(ctx, ip), c, mdKeys) + + return m, nil +} + +func (fs *ocfs) getMDShareFolder(ctx context.Context, sp string, mdKeys []string) (*provider.ResourceInfo, error) { + ip := fs.toInternalShadowPath(ctx, sp) + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.Stat { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + md, err := os.Stat(ip) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.toStorageShadowPath(ctx, ip)) + } + return nil, errors.Wrapf(err, "ocfs: error stating %s", ip) + } + c := fs.pool.Get() + defer c.Close() + m := fs.convertToResourceInfo(ctx, md, ip, fs.toStorageShadowPath(ctx, ip), c, mdKeys) + if !fs.isShareFolderRoot(sp) { + m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE + ref, err := xattr.Get(ip, mdPrefix+"target") + if err != nil { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStorageShadowPath(ctx, ip)) + } + return nil, err + } + m.Target = string(ref) + } + + return m, nil +} + +func (fs *ocfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { + log := appctx.GetLogger(ctx) + + ip, err := fs.resolve(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error resolving reference") + } + sp := fs.toStoragePath(ctx, ip) + + if fs.c.EnableHome { + log.Debug().Msg("home enabled") + if strings.HasPrefix(sp, "/") { + // permissions checked in listWithHome + return fs.listWithHome(ctx, "/", sp, mdKeys) + } + } + + log.Debug().Msg("list with nominal home") + // permissions checked in listWithNominalHome + return fs.listWithNominalHome(ctx, sp, mdKeys) +} + +func (fs *ocfs) listWithNominalHome(ctx context.Context, ip string, mdKeys []string) ([]*provider.ResourceInfo, error) { + + // If a user wants to list a folder shared with him the path will already + // be wrapped with the files directory path of the share owner. + // In that case we don't want to wrap the path again. + if !strings.HasPrefix(ip, fs.c.DataDirectory) { + ip = fs.toInternalPath(ctx, ip) + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListContainer { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + mds, err := ioutil.ReadDir(ip) + if err != nil { + return nil, errors.Wrapf(err, "ocfs: error listing %s", ip) + } + c := fs.pool.Get() + defer c.Close() + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + cp := filepath.Join(ip, md.Name()) + m := fs.convertToResourceInfo(ctx, md, cp, fs.toStoragePath(ctx, cp), c, mdKeys) + finfos = append(finfos, m) + } + return finfos, nil +} + +func (fs *ocfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { + log := appctx.GetLogger(ctx) + if p == home { + log.Debug().Msg("listing home") + return fs.listHome(ctx, home, mdKeys) + } + + if fs.isShareFolderRoot(p) { + log.Debug().Msg("listing share folder root") + return fs.listShareFolderRoot(ctx, p, mdKeys) + } + + if fs.isShareFolderChild(p) { + return nil, errtypes.PermissionDenied("ocfs: error listing folders inside the shared folder, only file references are stored inside") + } + + log.Debug().Msg("listing nominal home") + return fs.listWithNominalHome(ctx, p, mdKeys) +} + +func (fs *ocfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) { + // list files + ip := fs.toInternalPath(ctx, home) + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListContainer { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + mds, err := ioutil.ReadDir(ip) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error listing files") + } + + c := fs.pool.Get() + defer c.Close() + + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + cp := filepath.Join(ip, md.Name()) + m := fs.convertToResourceInfo(ctx, md, cp, fs.toStoragePath(ctx, cp), c, mdKeys) + finfos = append(finfos, m) + } + + // list shadow_files + ip = fs.toInternalShadowPath(ctx, home) + mds, err = ioutil.ReadDir(ip) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error listing shadow_files") + } + for _, md := range mds { + cp := filepath.Join(ip, md.Name()) + m := fs.convertToResourceInfo(ctx, md, cp, fs.toStorageShadowPath(ctx, cp), c, mdKeys) + finfos = append(finfos, m) + } + return finfos, nil +} + +func (fs *ocfs) listShareFolderRoot(ctx context.Context, sp string, mdKeys []string) ([]*provider.ResourceInfo, error) { + ip := fs.toInternalShadowPath(ctx, sp) + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListContainer { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + mds, err := ioutil.ReadDir(ip) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error listing shadow_files") + } + + c := fs.pool.Get() + defer c.Close() + + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + cp := filepath.Join(ip, md.Name()) + m := fs.convertToResourceInfo(ctx, md, cp, fs.toStorageShadowPath(ctx, cp), c, mdKeys) + m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE + ref, err := xattr.Get(cp, mdPrefix+"target") + if err != nil { + return nil, err + } + m.Target = string(ref) + finfos = append(finfos, m) + } + + return finfos, nil +} + +func (fs *ocfs) archiveRevision(ctx context.Context, vbp string, ip string) error { + // move existing file to versions dir + vp := fmt.Sprintf("%s.v%d", vbp, time.Now().Unix()) + if err := os.MkdirAll(filepath.Dir(vp), 0700); err != nil { + return errors.Wrap(err, "ocfs: error creating versions dir "+vp) + } + + // TODO(jfd): make sure rename is atomic, missing fsync ... + if err := os.Rename(ip, vp); err != nil { + return errors.Wrap(err, "ocfs: error renaming from "+ip+" to "+vp) + } + + return nil +} + +func (fs *ocfs) copyMD(s string, t string) (err error) { + var attrs []string + if attrs, err = xattr.List(s); err != nil { + return err + } + for i := range attrs { + if strings.HasPrefix(attrs[i], ocPrefix) { + var d []byte + if d, err = xattr.Get(s, attrs[i]); err != nil { + return err + } + if err = xattr.Set(t, attrs[i], d); err != nil { + return err + } + } + } + return nil +} + +func (fs *ocfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { + ip, err := fs.resolve(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.InitiateFileDownload { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + r, err := os.Open(ip) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) + } + return nil, errors.Wrap(err, "ocfs: error reading "+ip) + } + return r, nil +} + +func (fs *ocfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { + ip, err := fs.resolve(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListFileVersions { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + + vp := fs.getVersionsPath(ctx, ip) + + bn := filepath.Base(ip) + + revisions := []*provider.FileVersion{} + mds, err := ioutil.ReadDir(filepath.Dir(vp)) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error reading"+filepath.Dir(vp)) + } + for i := range mds { + rev := fs.filterAsRevision(ctx, bn, mds[i]) + if rev != nil { + revisions = append(revisions, rev) + } + + } + return revisions, nil +} + +func (fs *ocfs) filterAsRevision(ctx context.Context, bn string, md os.FileInfo) *provider.FileVersion { + if strings.HasPrefix(md.Name(), bn) { + // versions have filename.ext.v12345678 + version := md.Name()[len(bn)+2:] // truncate ".v" to get version mtime + mtime, err := strconv.Atoi(version) + if err != nil { + log := appctx.GetLogger(ctx) + log.Error().Err(err).Str("path", md.Name()).Msg("invalid version mtime") + return nil + } + // TODO(jfd) trashed versions are in the files_trashbin/versions folder ... not relevant here + return &provider.FileVersion{ + Key: version, + Size: uint64(md.Size()), + Mtime: uint64(mtime), + Etag: calcEtag(ctx, md), + } + } + return nil +} + +func (fs *ocfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { + return nil, errtypes.NotSupported("download revision") +} + +func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { + ip, err := fs.resolve(ctx, ref) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving reference") + } + + // check permissions + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.RestoreFileVersion { + return errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) + } + return errors.Wrap(err, "ocfs: error reading permissions") + } + + vp := fs.getVersionsPath(ctx, ip) + rp := vp + ".v" + revisionKey + + // check revision exists + rs, err := os.Stat(rp) + if err != nil { + return err + } + + if !rs.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", rp) + } + + source, err := os.Open(rp) + if err != nil { + return err + } + defer source.Close() + + // destination should be available, otherwise we could not have navigated to its revisions + if err := fs.archiveRevision(ctx, fs.getVersionsPath(ctx, ip), ip); err != nil { + return err + } + + destination, err := os.Create(ip) + if err != nil { + // TODO(jfd) bring back revision in case sth goes wrong? + return err + } + defer destination.Close() + + _, err = io.Copy(destination, source) + + if err != nil { + return err + } + + // TODO(jfd) bring back revision in case sth goes wrong? + return fs.propagate(ctx, ip) +} + +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, basePath, key, relativePath string) error { + rp, err := fs.getRecyclePath(ctx) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving recycle path") + } + ip := filepath.Join(rp, filepath.Clean(key)) + // TODO check permission? + + // check permissions + /* are they stored in the trash? + if perm, err := fs.readPermissions(ctx, ip); err == nil { + if !perm.ListContainer { + return nil, errtypes.PermissionDenied("") + } + } else { + if isNotFound(err) { + return nil, errtypes.NotFound(fs.unwrap(ctx, filepath.Dir(ip))) + } + return nil, errors.Wrap(err, "ocfs: error reading permissions") + } + */ + + err = os.Remove(ip) + if err != nil { + return errors.Wrap(err, "ocfs: error deleting recycle item") + } + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) + if err != nil { + return errors.Wrap(err, "ocfs: error deleting recycle item versions") + } + // TODO delete keyfiles, keys, share-keys + return nil +} + +func (fs *ocfs) EmptyRecycle(ctx context.Context) error { + // TODO check permission? on what? user must be the owner + rp, err := fs.getRecyclePath(ctx) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving recycle path") + } + err = os.RemoveAll(rp) + if err != nil { + return errors.Wrap(err, "ocfs: error deleting recycle files") + } + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions")) + if err != nil { + return errors.Wrap(err, "ocfs: error deleting recycle files versions") + } + // TODO delete keyfiles, keys, share-keys ... or just everything? + return nil +} + +func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileInfo) *provider.RecycleItem { + // trashbin items have filename.ext.d12345678 + suffix := filepath.Ext(md.Name()) + if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { + log := appctx.GetLogger(ctx) + log.Error().Str("path", md.Name()).Msg("invalid trash item suffix") + return nil + } + trashtime := suffix[2:] // truncate "d" to get trashbin time + ttime, err := strconv.Atoi(trashtime) + if err != nil { + log := appctx.GetLogger(ctx) + log.Error().Err(err).Str("path", md.Name()).Msg("invalid trash time") + return nil + } + var v []byte + if v, err = xattr.Get(filepath.Join(rp, md.Name()), trashOriginPrefix); err != nil { + log := appctx.GetLogger(ctx) + log.Error().Err(err).Str("path", md.Name()).Msg("could not read origin") + return nil + } + // ownCloud 10 stores the parent dir of the deleted item as the location in the oc_files_trashbin table + // we use extended attributes for original location, but also only the parent location, which is why + // we need to join and trim the path when listing it + originalPath := filepath.Join(string(v), strings.TrimSuffix(filepath.Base(md.Name()), suffix)) + + return &provider.RecycleItem{ + Type: getResourceType(md.IsDir()), + Key: md.Name(), + // TODO do we need to prefix the path? it should be relative to this storage root, right? + Ref: &provider.Reference{ + Path: originalPath, + }, + Size: uint64(md.Size()), + DeletionTime: &types.Timestamp{ + Seconds: uint64(ttime), + // no nanos available + }, + } +} + +func (fs *ocfs) ListRecycle(ctx context.Context, basePath, key, relativePath string) ([]*provider.RecycleItem, error) { + // TODO check permission? on what? user must be the owner? + rp, err := fs.getRecyclePath(ctx) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error resolving recycle path") + } + + // list files folder + mds, err := ioutil.ReadDir(filepath.Join(rp, key)) + if err != nil { + log := appctx.GetLogger(ctx) + log.Debug().Err(err).Str("path", rp).Msg("trash not readable") + // TODO jfd only ignore not found errors + return []*provider.RecycleItem{}, nil + } + // TODO (jfd) limit and offset + items := []*provider.RecycleItem{} + for i := range mds { + ri := fs.convertToRecycleItem(ctx, rp, mds[i]) + if ri != nil { + items = append(items, ri) + } + + } + return items, nil +} + +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, basePath, key, relativePath string, restoreRef *provider.Reference) error { + // TODO check permission? on what? user must be the owner? + log := appctx.GetLogger(ctx) + rp, err := fs.getRecyclePath(ctx) + if err != nil { + return errors.Wrap(err, "ocfs: error resolving recycle path") + } + src := filepath.Join(rp, filepath.Clean(key)) + + suffix := filepath.Ext(src) + if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { + log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") + return nil + } + + if restoreRef == nil { + restoreRef = &provider.Reference{} + } + if restoreRef.Path == "" { + v, err := xattr.Get(src, trashOriginPrefix) + if err != nil { + log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") + } + restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) + } + tgt := fs.toInternalPath(ctx, restoreRef.Path) + // move back to original location + if err := os.Rename(src, tgt); err != nil { + log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + return errors.Wrap(err, "ocfs: could not restore item") + } + // unset trash origin location in metadata + if err := xattr.Remove(tgt, trashOriginPrefix); err != nil { + // just a warning, will be overwritten next time it is deleted + log.Warn().Err(err).Str("key", key).Str("tgt", tgt).Msg("could not unset origin") + } + // TODO(jfd) restore versions + + return fs.propagate(ctx, tgt) +} + +func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { + return nil, errtypes.NotSupported("list storage spaces") +} + +// UpdateStorageSpace updates a storage space +func (fs *ocfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + +func (fs *ocfs) propagate(ctx context.Context, leafPath string) error { + var root string + if fs.c.EnableHome { + root = fs.toInternalPath(ctx, "/") + } else { + owner := fs.getOwner(leafPath) + root = fs.toInternalPath(ctx, owner) + } + if !strings.HasPrefix(leafPath, root) { + err := errors.New("internal path outside root") + appctx.GetLogger(ctx).Error(). + Err(err). + Str("leafPath", leafPath). + Str("root", root). + Msg("could not propagate change") + return err + } + + fi, err := os.Stat(leafPath) + if err != nil { + appctx.GetLogger(ctx).Error(). + Err(err). + Str("leafPath", leafPath). + Str("root", root). + Msg("could not propagate change") + return err + } + + parts := strings.Split(strings.TrimPrefix(leafPath, root), "/") + // root never ends in / so the split returns an empty first element, which we can skip + // we do not need to chmod the last element because it is the leaf path (< and not <= comparison) + for i := 1; i < len(parts); i++ { + appctx.GetLogger(ctx).Debug(). + Str("leafPath", leafPath). + Str("root", root). + Int("i", i). + Interface("parts", parts). + Msg("propagating change") + if err := os.Chtimes(filepath.Join(root), fi.ModTime(), fi.ModTime()); err != nil { + appctx.GetLogger(ctx).Error(). + Err(err). + Str("leafPath", leafPath). + Str("root", root). + Msg("could not propagate change") + return err + } + root = filepath.Join(root, parts[i]) + } + return nil +} + +func readChecksumIntoResourceChecksum(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { + v, err := xattr.Get(nodePath, checksumPrefix+algo) + log := appctx.GetLogger(ctx). + Debug(). + Err(err). + Str("nodepath", nodePath). + Str("algorithm", algo) + switch { + case err == nil: + ri.Checksum = &provider.ResourceChecksum{ + Type: storageprovider.PKG2GRPCXS(algo), + Sum: hex.EncodeToString(v), + } + case isNoData(err): + log.Msg("checksum not set") + case isNotFound(err): + log.Msg("file not found") + default: + log.Msg("could not read checksum") + } +} + +func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { + v, err := xattr.Get(nodePath, checksumPrefix+algo) + log := appctx.GetLogger(ctx). + Debug(). + Err(err). + Str("nodepath", nodePath). + Str("algorithm", algo) + switch { + case err == nil: + if ri.Opaque == nil { + ri.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{}, + } + } + ri.Opaque.Map[algo] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(hex.EncodeToString(v)), + } + case isNoData(err): + log.Msg("checksum not set") + case isNotFound(err): + log.Msg("file not found") + default: + log.Msg("could not read checksum") + } +} + +// TODO propagate etag and mtime or append event to history? propagate on disk ... +// - but propagation is a separate task. only if upload was successful ... diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index f64978ccb9..1c316825d8 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1062,7 +1062,7 @@ func (fs *owncloudsqlfs) RefreshLock(ctx context.Context, ref *provider.Referenc } // Unlock removes an existing lock from the given reference -func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { +func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("unimplemented") } @@ -1972,6 +1972,16 @@ func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) { } } +func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { + // TODO(corby): Implement + return nil, errtypes.NotSupported("list storage spaces") +} + +// UpdateStorageSpace updates a storage space +func (fs *owncloudsqlfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) { re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`) matches := re.FindStringSubmatch(checksums) diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index c3947deb93..b0cd7b5247 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -296,7 +296,7 @@ func (fs *s3FS) RefreshLock(ctx context.Context, ref *provider.Reference, lock * } // Unlock removes an existing lock from the given reference -func (fs *s3FS) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { +func (fs *s3FS) Unlock(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index c116ec076b..c328fffb37 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -61,7 +61,7 @@ type FS interface { SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error - Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error + Unlock(ctx context.Context, ref *provider.Reference) error ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/utils/acl/acl.go index 311ee1a454..32ab9c3d25 100644 --- a/pkg/storage/utils/acl/acl.go +++ b/pkg/storage/utils/acl/acl.go @@ -67,10 +67,6 @@ func Parse(acls string, delimiter string) (*ACLs, error) { if err != nil { return nil, err } - // for now we ignore default / empty qualifiers - // if entry.Qualifier == "" { - // continue - // } entries = append(entries, entry) } @@ -128,14 +124,43 @@ type Entry struct { // ParseEntry parses a single ACL func ParseEntry(singleSysACL string) (*Entry, error) { tokens := strings.Split(singleSysACL, ":") - if len(tokens) != 3 { + switch len(tokens) { + case 2: + // The ACL entries might be stored as type:qualifier=permissions + // Handle that case separately + parts := strings.SplitN(tokens[1], "=", 2) + if len(parts) == 2 { + return &Entry{ + Type: tokens[0], + Qualifier: parts[0], + Permissions: parts[1], + }, nil + } + case 3: + return &Entry{ + Type: tokens[0], + Qualifier: tokens[1], + Permissions: tokens[2], + }, nil + } + return nil, errInvalidACL +} + +// ParseLWEntry parses a single lightweight ACL +func ParseLWEntry(singleSysACL string) (*Entry, error) { + if !strings.HasPrefix(singleSysACL, TypeLightweight+":") { return nil, errInvalidACL } + singleSysACL = strings.TrimPrefix(singleSysACL, TypeLightweight+":") + tokens := strings.Split(singleSysACL, "=") + if len(tokens) != 2 { + return nil, errInvalidACL + } return &Entry{ - Type: tokens[0], - Qualifier: tokens[1], - Permissions: tokens[2], + Type: TypeLightweight, + Qualifier: tokens[0], + Permissions: tokens[1], }, nil } diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 849aa27cb7..30ba8e1832 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -37,21 +37,20 @@ import ( cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/logger" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/storage" - "github.com/cs3org/reva/v2/pkg/storage/utils/chunking" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/xattrs" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" - rtrace "github.com/cs3org/reva/v2/pkg/trace" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/logger" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/utils/chunking" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" + "github.com/cs3org/reva/pkg/storage/utils/templates" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" "go.opentelemetry.io/otel/codes" "google.golang.org/grpc" @@ -113,6 +112,10 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore) (storage.FS, error) tp := tree.New(o.Root, o.TreeTimeAccounting, o.TreeSizeAccounting, lu, bs) + o.GatewayAddr = sharedconf.GetGatewaySVC(o.GatewayAddr) + return New(o, lu, p, tp) +} + permissionsClient, err := pool.GetPermissionsClient(o.PermissionsSVC) if err != nil { return nil, err @@ -577,104 +580,20 @@ func (fs *Decomposedfs) Download(ctx context.Context, ref *provider.Reference) ( // GetLock returns an existing lock on the given reference func (fs *Decomposedfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { - node, err := fs.lu.NodeFromResource(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "Decomposedfs: error resolving ref") - } - - if !node.Exists { - err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) - return nil, err - } - - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.InitiateFileDownload - }) - switch { - case err != nil: - return nil, errtypes.InternalError(err.Error()) - case !ok: - return nil, errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) - } - return node.ReadLock(ctx, false) + return nil, errtypes.NotSupported("unimplemented") } // SetLock puts a lock on the given reference func (fs *Decomposedfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - node, err := fs.lu.NodeFromResource(ctx, ref) - if err != nil { - return errors.Wrap(err, "Decomposedfs: error resolving ref") - } - - if !node.Exists { - return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) - } - - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.InitiateFileUpload - }) - switch { - case err != nil: - return errtypes.InternalError(err.Error()) - case !ok: - return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) - } - - return node.SetLock(ctx, lock) + return errtypes.NotSupported("unimplemented") } // RefreshLock refreshes an existing lock on the given reference func (fs *Decomposedfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - if lock.LockId == "" { - return errtypes.BadRequest("missing lockid") - } - - node, err := fs.lu.NodeFromResource(ctx, ref) - if err != nil { - return errors.Wrap(err, "Decomposedfs: error resolving ref") - } - - if !node.Exists { - return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) - } - - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.InitiateFileUpload - }) - switch { - case err != nil: - return errtypes.InternalError(err.Error()) - case !ok: - return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) - } - - return node.RefreshLock(ctx, lock) + return errtypes.NotSupported("unimplemented") } // Unlock removes an existing lock from the given reference -func (fs *Decomposedfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - if lock.LockId == "" { - return errtypes.BadRequest("missing lockid") - } - - node, err := fs.lu.NodeFromResource(ctx, ref) - if err != nil { - return errors.Wrap(err, "Decomposedfs: error resolving ref") - } - - if !node.Exists { - return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) - } - - ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { - return rp.InitiateFileUpload // TODO do we need a dedicated permission? - }) - switch { - case err != nil: - return errtypes.InternalError(err.Error()) - case !ok: - return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) - } - - return node.Unlock(ctx, lock) +func (fs *Decomposedfs) Unlock(ctx context.Context, ref *provider.Reference) error { + return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/utils/decomposedfs/options/options.go b/pkg/storage/utils/decomposedfs/options/options.go index 16811837a7..1d12a3245a 100644 --- a/pkg/storage/utils/decomposedfs/options/options.go +++ b/pkg/storage/utils/decomposedfs/options/options.go @@ -46,11 +46,12 @@ type Options struct { // propagate size changes as treesize TreeSizeAccounting bool `mapstructure:"treesize_accounting"` - // permissions service to use when checking permissions - PermissionsSVC string `mapstructure:"permissionssvc"` + // set an owner for the root node + Owner string `mapstructure:"owner"` + OwnerIDP string `mapstructure:"owner_idp"` + OwnerType string `mapstructure:"owner_type"` - PersonalSpaceAliasTemplate string `mapstructure:"personalspacealias_template"` - GeneralSpaceAliasTemplate string `mapstructure:"generalspacealias_template"` + GatewayAddr string `mapstructure:"gateway_addr"` } // New returns a new Options instance for the given configuration diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index aa33f1d360..47bdec6a77 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -30,21 +30,17 @@ import ( "time" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - ocsconv "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" - "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/xattrs" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + ocsconv "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" ) @@ -308,28 +304,27 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return spaces, nil } - matches := []string{} - for _, spaceType := range spaceTypes { - path := filepath.Join(fs.o.Root, "spacetypes", spaceType, nodeID) - m, err := filepath.Glob(path) - if err != nil { - return nil, err - } - matches = append(matches, m...) + client, err := pool.GetGatewayServiceClient(fs.o.GatewayAddr) + if err != nil { + return nil, err } - // FIXME if the space does not exist try a node as the space root. - - // But then the whole /spaces/{spaceType}/{spaceid} becomes obsolete - // we can alway just look up by nodeid - // -> no. The /spaces folder is used for efficient lookup by type, otherwise we would have - // to iterate over all nodes and read the type from extended attributes - // -> but for lookup by id we can use the node directly. - // But what about sharding nodes by space? - // an efficient lookup would be possible if we received a spaceid&opaqueid in the request - // the personal spaces must also use the nodeid and not the name + checkRes, err := client.CheckPermission(ctx, &permissionsv1beta1.CheckPermissionRequest{ + Permission: "list-all-spaces", + SubjectRef: &permissionsv1beta1.SubjectReference{ + Spec: &permissionsv1beta1.SubjectReference_UserId{ + UserId: u.Id, + }, + }, + }) + if err != nil { + return nil, err + } - numShares := 0 + canListAllSpaces := false + if checkRes.Status.Code == v1beta11.Code_CODE_OK { + canListAllSpaces = true + } for i := range matches { var err error @@ -373,16 +368,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } spaces = append(spaces, space) - } - // if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space - if len(matches) <= numShares && nodeID != spaceID { - // try node id - n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID) - if err != nil { - return nil, err - } - if n.Exists { - space, err := fs.storageSpaceFromNode(ctx, n, n.InternalPath(), canListAllSpaces) + // TODO apply more filters + space, err := fs.storageSpaceFromNode(ctx, n, matches[i], spaceType, canListAllSpaces) if err != nil { return nil, err } @@ -415,9 +402,13 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up } } - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - return nil, fmt.Errorf("decomposedfs: spaces: contextual user not found") + if len(matches) != 1 { + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{ + Code: v1beta11.Code_CODE_NOT_FOUND, + Message: fmt.Sprintf("update space failed: found %d matching spaces", len(matches)), + }, + }, nil } space.Owner = u @@ -430,33 +421,15 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up metadata[xattrs.QuotaAttr] = strconv.FormatUint(space.Quota.QuotaMaxBytes, 10) } - // TODO also return values which are not in the request - hasDescription := false - if space.Opaque != nil { - if description, ok := space.Opaque.Map["description"]; ok { - metadata[xattrs.SpaceDescriptionAttr] = string(description.Value) - hasDescription = true - } - if alias := utils.ReadPlainFromOpaque(space.Opaque, "spaceAlias"); alias != "" { - metadata[xattrs.SpaceAliasAttr] = alias - } - if image := utils.ReadPlainFromOpaque(space.Opaque, "image"); image != "" { - imageID := resourceid.OwnCloudResourceIDUnwrap(image) - if imageID == nil { - return &provider.UpdateStorageSpaceResponse{ - Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space image resource not found"}, - }, nil - } - metadata[xattrs.SpaceImageAttr] = imageID.OpaqueId - } - if readme := utils.ReadPlainFromOpaque(space.Opaque, "readme"); readme != "" { - readmeID := resourceid.OwnCloudResourceIDUnwrap(readme) - if readmeID == nil { - return &provider.UpdateStorageSpaceResponse{ - Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space readme resource not found"}, - }, nil - } - metadata[xattrs.SpaceReadmeAttr] = readmeID.OpaqueId + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return nil, fmt.Errorf("decomposedfs: spaces: contextual user not found") + } + space.Owner = u + + if space.Name != "" { + if err := node.SetMetadata(xattrs.SpaceNameAttr, space.Name); err != nil { + return nil, err } } @@ -562,43 +535,8 @@ func (fs *Decomposedfs) linkStorageSpaceType(ctx context.Context, spaceType stri return err } -func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, nodePath string, canListAllSpaces bool) (*provider.StorageSpace, error) { - user := ctxpkg.ContextMustGetUser(ctx) - if !canListAllSpaces { - ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { - return p.Stat - }) - if err != nil || !ok { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.ID)) - } - - if n.SpaceRoot.IsDisabled() { - ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { - // TODO: Which permission do I need to see the space? - return p.AddGrant - }) - if err != nil || !ok { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.ID)) - } - } - } - - var err error - // TODO apply more filters - var sname string - if sname, err = n.SpaceRoot.GetMetadata(xattrs.SpaceNameAttr); err != nil { - // FIXME: Is that a severe problem? - appctx.GetLogger(ctx).Debug().Err(err).Msg("space does not have a name attribute") - } - - /* - if err := n.FindStorageSpaceRoot(); err != nil { - return nil, err - } - */ - - // read the grants from the current node, not the root - grants, err := n.ListGrants(ctx) +func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, node *node.Node, nodePath, spaceType string, canListAllSpaces bool) (*provider.StorageSpace, error) { + owner, err := node.Owner() if err != nil { return nil, err } @@ -641,12 +579,17 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, // Mtime is set either as node.tmtime or as fi.mtime below } - if space.SpaceType, err = n.SpaceRoot.GetMetadata(xattrs.SpaceTypeAttr); err != nil { - appctx.GetLogger(ctx).Debug().Err(err).Msg("space does not have a type attribute") - } + user := ctxpkg.ContextMustGetUser(ctx) - if n.SpaceRoot.IsDisabled() { - space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "trashed", "trashed") + // filter out spaces user cannot access (currently based on stat permission) + if !canListAllSpaces { + p, err := node.ReadUserPermissions(ctx, user) + if err != nil { + return nil, err + } + if !p.Stat { + return nil, errors.New("user is not allowed to Stat the space") + } } if n.Owner() != nil && n.Owner().OpaqueId != "" { diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index 869f41d254..7d976585ae 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -314,7 +314,7 @@ func (fs *Decomposedfs) GetUpload(ctx context.Context, id string) (tusd.Upload, info := tusd.FileInfo{} data, err := ioutil.ReadFile(infoPath) if err != nil { - if errors.Is(err, iofs.ErrNotExist) { + if os.IsNotExist(err) { // Interpret os.ErrNotExist as 404 Not Found err = tusd.ErrNotFound } diff --git a/pkg/storage/utils/eosfs/config.go b/pkg/storage/utils/eosfs/config.go index 53cc6a3992..5b6838e696 100644 --- a/pkg/storage/utils/eosfs/config.go +++ b/pkg/storage/utils/eosfs/config.go @@ -140,6 +140,10 @@ type Config struct { // Only considered when EnableHome is false. AllowPathRecycleOperations bool `mapstructure:"allow_path_recycle_operations"` + // Whether we should impersonate the owner of a resource when trying to perform + // revisions-related operations. + ImpersonateOwnerforRevisions bool `mapstructure:"impersonate_owner_for_revisions"` + // HTTP connections to EOS: max number of idle conns MaxIdleConns int `mapstructure:"max_idle_conns"` diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 4fd779fcb2..f794211736 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -365,6 +365,47 @@ func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (str return external, nil } +func (fs *eosfs) resolveRefForbidShareFolder(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { + p, err := fs.resolve(ctx, ref) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") + } + if fs.isShareFolder(ctx, p) { + return "", eosclient.Authorization{}, errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") + } + fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return "", eosclient.Authorization{}, err + } + + return fn, auth, nil +} + +func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { + p, err := fs.resolve(ctx, ref) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") + } + fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return "", eosclient.Authorization{}, err + } + + return fn, auth, nil +} + // resolve takes in a request path or request id and returns the unwrapped path. func (fs *eosfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { if ref.ResourceId != nil { @@ -426,7 +467,7 @@ func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (stri if err != nil { return "", errors.Wrap(err, "eosfs: no user in ctx") } - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { auth, err := fs.getRootAuth(ctx) if err != nil { return "", err @@ -459,19 +500,9 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen return errtypes.BadRequest("eosfs: no metadata set") } - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return err } for k, v := range md.Metadata { @@ -501,19 +532,9 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer return errtypes.BadRequest("eosfs: no keys set") } - p, err := fs.resolve(ctx, ref) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return err } for _, k := range keys { @@ -551,23 +572,12 @@ func (fs *eosfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock } // Unlock removes an existing lock from the given reference -func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { +func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("unimplemented") } func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } @@ -594,23 +604,9 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi } func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - - fn := fs.wrap(ctx, p) - - // eos does not offer a permission bit to specify if the - // user can deny or not. We need to take care of that in Reva - // by checking context user has permission to deny - finfo, err := fs.GetMD(ctx, ref, nil) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrapf(err, "eosfs: error getting metadata for file ref: %+v", ref) - } - - if !finfo.PermissionSet.DenyGrant { - return errtypes.PermissionDenied(fmt.Sprintf("eosfs: context user cannot deny access to ref: %+v", ref)) + return err } position := eosclient.EndPosition @@ -626,16 +622,6 @@ func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *prov Permissions: &provider.ResourcePermissions{}, } - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return err - } - eosACL, err := fs.getEosACL(ctx, grant) if err != nil { return err @@ -661,7 +647,8 @@ func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, var qualifier string if t == acl.TypeUser { // if the grantee is a lightweight account, we need to set it accordingly - if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || + g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED { t = acl.TypeLightweight qualifier = g.Grantee.GetUserId().OpaqueId } else { @@ -694,7 +681,8 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr var recipient string if eosACLType == acl.TypeUser { // if the grantee is a lightweight account, we need to set it accordingly - if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || + g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED { eosACLType = acl.TypeLightweight recipient = g.Grantee.GetUserId().OpaqueId } else { @@ -714,17 +702,7 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr Type: eosACLType, } - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } @@ -746,17 +724,7 @@ func (fs *eosfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *pr } func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return nil, err - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return nil, err } @@ -813,7 +781,8 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st } fn := "" - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || + u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { p, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "eosfs: error resolving reference") @@ -1037,7 +1006,7 @@ func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, // lightweight accounts don't have quota nodes, so we're passing an empty string as path auth, err := fs.getUserAuth(ctx, u, "") if err != nil { - return 0, 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") + return 0, 0, err } rootAuth, err := fs.getRootAuth(ctx) @@ -1223,28 +1192,42 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { log := appctx.GetLogger(ctx) - u, err := getUser(ctx) + p, err := fs.resolve(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") + return errors.Wrap(err, "eosfs: error resolving reference") } - p, err := fs.resolve(ctx, ref) + if fs.isShareFolder(ctx, p) { + return errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") + } + fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) if err != nil { - return nil + return errors.Wrap(err, "eosfs: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, p) + // We need the auth corresponding to the parent directory + // as the file might not exist at the moment + auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) if err != nil { return err } - log.Info().Msgf("eosfs: createdir: path=%s", p) + log.Info().Msgf("eosfs: createdir: path=%s", fn) + return fs.c.CreateDir(ctx, auth, fn) +} - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot create folder under the share folder") +// TouchFile as defined in the storage.FS interface +func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + log := appctx.GetLogger(ctx) + + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) + if err != nil { + return err } + log.Info().Msgf("eosfs: touch file: path=%s", fn) - fn := fs.wrap(ctx, p) - return fs.c.CreateDir(ctx, auth, fn) + return fs.c.Touch(ctx, auth, fn) } // TouchFile as defined in the storage.FS interface @@ -1423,22 +1406,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error } func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot download under the virtual share folder") - } - - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref) if err != nil { return nil, err } @@ -1447,24 +1415,33 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read } func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot list revisions under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return nil, err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return nil, err + if md.PermissionSet.ListFileVersions { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return nil, err + } + } else { + return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to list revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return nil, err + } } eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) @@ -1481,48 +1458,66 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] } func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot download revision under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return nil, err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return nil, err + if md.PermissionSet.InitiateFileDownload { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return nil, err + } + } else { + return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return nil, err + } } return fs.c.ReadVersion(ctx, auth, fn, revisionKey) } func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot restore revision under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return err + if md.PermissionSet.RestoreFileVersion { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return err + } + } else { + return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return err + } } return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey) @@ -1924,7 +1919,8 @@ func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (eosclient return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting gateway grpc client") } getUserResp, err := client.GetUser(ctx, &userpb.GetUserRequest{ - UserId: u, + UserId: u, + SkipFetchingUserGroups: true, }) if err != nil { _ = fs.userIDCache.SetWithTTL(u.OpaqueId, &userpb.User{}, 12*time.Hour) @@ -1957,8 +1953,9 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User return nil, errors.Wrap(err, "eosfs: error getting gateway grpc client") } getUserResp, err := client.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "uid", - Value: uid, + Claim: "uid", + Value: uid, + SkipFetchingUserGroups: true, }) if err != nil { // Insert an empty object in the cache so that we don't make another call @@ -1987,7 +1984,8 @@ func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, fn string) (eo return fs.singleUserAuth, err } - if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || + u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { return fs.getEOSToken(ctx, u, fn) } @@ -2005,7 +2003,7 @@ func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eo } info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn) if err != nil { - return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting file info by path") + return eosclient.Authorization{}, err } auth := eosclient.Authorization{ Role: eosclient.Role{ diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 2d8e27b483..9bae497ac0 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -22,6 +22,7 @@ import ( "context" "io" "os" + "path" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/errtypes" @@ -63,7 +64,10 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC if err != nil { return errors.Wrap(err, "eos: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, fn) + + // We need the auth corresponding to the parent directory + // as the file might not exist at the moment + auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) if err != nil { return err } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 00ca98b32a..c2cd4b2c6b 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -76,7 +76,7 @@ func (c *Config) init() { } if c.DataTransfersFolder == "" { - c.DataTransfersFolder = "/Data-Transfers" + c.DataTransfersFolder = "/DataTransfers" } // ensure share folder always starts with slash @@ -731,7 +731,7 @@ func (fs *localfs) RefreshLock(ctx context.Context, ref *provider.Reference, loc } // Unlock removes an existing lock from the given reference -func (fs *localfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { +func (fs *localfs) Unlock(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/trace/trace.go b/pkg/trace/trace.go index bc506d9e58..a20a9994ec 100644 --- a/pkg/trace/trace.go +++ b/pkg/trace/trace.go @@ -40,7 +40,12 @@ var ( ) // SetTraceProvider sets the TracerProvider at a package level. -func SetTraceProvider(collectorEndpoint string, agentEndpoint string) { +func SetTraceProvider(collectorEndpoint string, agentEndpoint, serviceName string) { + // default to 'reva' as service name if not set + if serviceName == "" { + serviceName = "reva" + } + var exp *jaeger.Exporter var err error @@ -75,7 +80,7 @@ func SetTraceProvider(collectorEndpoint string, agentEndpoint string) { sdktrace.WithBatcher(exp), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNameKey.String("reva"), + semconv.ServiceNameKey.String(serviceName), )), ) } diff --git a/pkg/user/manager/demo/demo.go b/pkg/user/manager/demo/demo.go index 68dd4a62ef..5a429fa002 100644 --- a/pkg/user/manager/demo/demo.go +++ b/pkg/user/manager/demo/demo.go @@ -54,19 +54,27 @@ func (m *manager) Configure(ml map[string]interface{}) error { return nil } -func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { if user, ok := m.catalog[uid.OpaqueId]; ok { if uid.Idp == "" || user.Id.Idp == uid.Idp { - return user, nil + u := *user + if skipFetchingGroups { + u.Groups = nil + } + return &u, nil } } return nil, errtypes.NotFound(uid.OpaqueId) } -func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { for _, u := range m.catalog { if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim { - return u, nil + user := *u + if skipFetchingGroups { + user.Groups = nil + } + return &user, nil } } return nil, errtypes.NotFound(value) @@ -91,18 +99,22 @@ func userContains(u *userpb.User, query string) bool { return strings.Contains(u.Username, query) || strings.Contains(u.DisplayName, query) || strings.Contains(u.Mail, query) || strings.Contains(u.Id.OpaqueId, query) } -func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { users := []*userpb.User{} for _, u := range m.catalog { if userContains(u, query) { - users = append(users, u) + user := *u + if skipFetchingGroups { + user.Groups = nil + } + users = append(users, &user) } } return users, nil } func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { - user, err := m.GetUser(ctx, uid) + user, err := m.GetUser(ctx, uid, false) if err != nil { return nil, err } diff --git a/pkg/user/manager/demo/demo_test.go b/pkg/user/manager/demo/demo_test.go index ee8473c7f6..c92a03550b 100644 --- a/pkg/user/manager/demo/demo_test.go +++ b/pkg/user/manager/demo/demo_test.go @@ -44,49 +44,64 @@ func TestUserManager(t *testing.T) { UidNumber: 123, GidNumber: 987, } - uidFake := &userpb.UserId{Idp: "nonesense", OpaqueId: "fakeUser"} - groupsEinstein := []string{"sailing-lovers", "violin-haters", "physics-lovers"} - - // positive test GetUserGroups - resGroups, _ := manager.GetUserGroups(ctx, uidEinstein) - if !reflect.DeepEqual(resGroups, groupsEinstein) { - t.Fatalf("groups differ: expected=%v got=%v", groupsEinstein, resGroups) + userEinsteinWithoutGroups := &userpb.User{ + Id: uidEinstein, + Username: "einstein", + Mail: "einstein@example.org", + DisplayName: "Albert Einstein", + UidNumber: 123, + GidNumber: 987, } - // negative test GetUserGroups - expectedErr := errtypes.NotFound(uidFake.OpaqueId) - _, err := manager.GetUserGroups(ctx, uidFake) - if !reflect.DeepEqual(err, expectedErr) { - t.Fatalf("user not found error differs: expected='%v' got='%v'", expectedErr, err) - } + uidFake := &userpb.UserId{Idp: "nonesense", OpaqueId: "fakeUser"} + groupsEinstein := []string{"sailing-lovers", "violin-haters", "physics-lovers"} // positive test GetUserByClaim by uid - resUserByUID, _ := manager.GetUserByClaim(ctx, "uid", "123") + resUserByUID, _ := manager.GetUserByClaim(ctx, "uid", "123", false) if !reflect.DeepEqual(resUserByUID, userEinstein) { t.Fatalf("user differs: expected=%v got=%v", userEinstein, resUserByUID) } // negative test GetUserByClaim by uid - expectedErr = errtypes.NotFound("789") - _, err = manager.GetUserByClaim(ctx, "uid", "789") + expectedErr := errtypes.NotFound("789") + _, err := manager.GetUserByClaim(ctx, "uid", "789", false) if !reflect.DeepEqual(err, expectedErr) { t.Fatalf("user not found error differs: expected='%v' got='%v'", expectedErr, err) } // positive test GetUserByClaim by mail - resUserByEmail, _ := manager.GetUserByClaim(ctx, "mail", "einstein@example.org") + resUserByEmail, _ := manager.GetUserByClaim(ctx, "mail", "einstein@example.org", false) if !reflect.DeepEqual(resUserByEmail, userEinstein) { t.Fatalf("user differs: expected=%v got=%v", userEinstein, resUserByEmail) } + // positive test GetUserByClaim by uid without groups + resUserByUIDWithoutGroups, _ := manager.GetUserByClaim(ctx, "uid", "123", true) + if !reflect.DeepEqual(resUserByUIDWithoutGroups, userEinsteinWithoutGroups) { + t.Fatalf("user differs: expected=%v got=%v", userEinsteinWithoutGroups, resUserByUIDWithoutGroups) + } + + // positive test GetUserGroups + resGroups, _ := manager.GetUserGroups(ctx, uidEinstein) + if !reflect.DeepEqual(resGroups, groupsEinstein) { + t.Fatalf("groups differ: expected=%v got=%v", groupsEinstein, resGroups) + } + + // negative test GetUserGroups + expectedErr = errtypes.NotFound(uidFake.OpaqueId) + _, err = manager.GetUserGroups(ctx, uidFake) + if !reflect.DeepEqual(err, expectedErr) { + t.Fatalf("user not found error differs: expected='%v' got='%v'", expectedErr, err) + } + // test FindUsers - resUser, _ := manager.FindUsers(ctx, "einstein") + resUser, _ := manager.FindUsers(ctx, "einstein", false) if !reflect.DeepEqual(resUser, []*userpb.User{userEinstein}) { t.Fatalf("user differs: expected=%v got=%v", []*userpb.User{userEinstein}, resUser) } // negative test FindUsers - resUsers, _ := manager.FindUsers(ctx, "notARealUser") + resUsers, _ := manager.FindUsers(ctx, "notARealUser", false) if len(resUsers) > 0 { t.Fatalf("user not in group: expected=%v got=%v", []*userpb.User{}, resUsers) } diff --git a/pkg/user/manager/json/json.go b/pkg/user/manager/json/json.go index a0cc6f1d96..cd4d49ec89 100644 --- a/pkg/user/manager/json/json.go +++ b/pkg/user/manager/json/json.go @@ -94,19 +94,27 @@ func (m *manager) Configure(ml map[string]interface{}) error { return nil } -func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { for _, u := range m.users { if (u.Id.GetOpaqueId() == uid.OpaqueId || u.Username == uid.OpaqueId) && (uid.Idp == "" || uid.Idp == u.Id.GetIdp()) { - return u, nil + user := *u + if skipFetchingGroups { + user.Groups = nil + } + return &user, nil } } return nil, errtypes.NotFound(uid.OpaqueId) } -func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { for _, u := range m.users { if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim { - return u, nil + user := *u + if skipFetchingGroups { + user.Groups = nil + } + return &user, nil } } return nil, errtypes.NotFound(value) @@ -135,18 +143,22 @@ func userContains(u *userpb.User, query string) bool { strings.Contains(strings.ToLower(u.Mail), query) || strings.Contains(strings.ToLower(u.Id.OpaqueId), query) } -func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { users := []*userpb.User{} for _, u := range m.users { if userContains(u, query) { - users = append(users, u) + user := *u + if skipFetchingGroups { + user.Groups = nil + } + users = append(users, &user) } } return users, nil } func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { - user, err := m.GetUser(ctx, uid) + user, err := m.GetUser(ctx, uid, false) if err != nil { return nil, err } diff --git a/pkg/user/manager/json/json_test.go b/pkg/user/manager/json/json_test.go index 796296ada9..a422bb051b 100644 --- a/pkg/user/manager/json/json_test.go +++ b/pkg/user/manager/json/json_test.go @@ -97,15 +97,15 @@ func TestUserManager(t *testing.T) { Mail: "einstein@example.org", DisplayName: "Albert Einstein", } + userEinsteinWithoutGroups := &userpb.User{ + Id: uidEinstein, + Username: "einstein", + Mail: "einstein@example.org", + DisplayName: "Albert Einstein", + } userFake := &userpb.UserId{Idp: "localhost", OpaqueId: "fakeUser", Type: userpb.UserType_USER_TYPE_PRIMARY} groupsEinstein := []string{"sailing-lovers", "violin-haters", "physics-lovers"} - // positive test GetUserGroups - resGroups, _ := manager.GetUserGroups(ctx, uidEinstein) - if !reflect.DeepEqual(resGroups, groupsEinstein) { - t.Fatalf("groups differ: expected=%v got=%v", resGroups, groupsEinstein) - } - // negative test GetUserGroups expectedErr := errtypes.NotFound(userFake.OpaqueId) _, err = manager.GetUserGroups(ctx, userFake) @@ -114,20 +114,32 @@ func TestUserManager(t *testing.T) { } // positive test GetUserByClaim by mail - resUserByEmail, _ := manager.GetUserByClaim(ctx, "mail", "einstein@example.org") + resUserByEmail, _ := manager.GetUserByClaim(ctx, "mail", "einstein@example.org", false) if !reflect.DeepEqual(resUserByEmail, userEinstein) { t.Fatalf("user differs: expected=%v got=%v", userEinstein, resUserByEmail) } // negative test GetUserByClaim by mail expectedErr = errtypes.NotFound("abc@example.com") - _, err = manager.GetUserByClaim(ctx, "mail", "abc@example.com") + _, err = manager.GetUserByClaim(ctx, "mail", "abc@example.com", false) if !reflect.DeepEqual(err, expectedErr) { t.Fatalf("user not found error differs: expected='%v' got='%v'", expectedErr, err) } + // positive test GetUserByClaim by mail without groups + resUserByEmailWithoutGroups, _ := manager.GetUserByClaim(ctx, "mail", "einstein@example.org", true) + if !reflect.DeepEqual(resUserByEmailWithoutGroups, userEinsteinWithoutGroups) { + t.Fatalf("user differs: expected=%v got=%v", userEinsteinWithoutGroups, resUserByEmailWithoutGroups) + } + + // positive test GetUserGroups + resGroups, _ := manager.GetUserGroups(ctx, uidEinstein) + if !reflect.DeepEqual(resGroups, groupsEinstein) { + t.Fatalf("groups differ: expected=%v got=%v", resGroups, groupsEinstein) + } + // test FindUsers - resUser, _ := manager.FindUsers(ctx, "stein") + resUser, _ := manager.FindUsers(ctx, "stein", false) if len(resUser) != 1 { t.Fatalf("too many users found: expected=%d got=%d", 1, len(resUser)) } diff --git a/pkg/user/manager/ldap/ldap.go b/pkg/user/manager/ldap/ldap.go index aa46a537cf..1fc3bb89b1 100644 --- a/pkg/user/manager/ldap/ldap.go +++ b/pkg/user/manager/ldap/ldap.go @@ -125,9 +125,7 @@ func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User return u, nil } -// GetUserByClaim implements the user.Manager interface. Looks up a user by -// claim ('mail', 'username', 'userid') and returns the user. -func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { log := appctx.GetLogger(ctx) log.Debug().Str("claim", claim).Str("value", value).Msg("GetUserByClaim") @@ -151,21 +149,24 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use u.Groups = groups - return u, nil -} + id := &userpb.UserId{ + Idp: m.c.Idp, + OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, + } -// FindUser implements the user.Manager interface. Searches for users using a prefix-substring search on -// the user attributes ('mail', 'username', 'displayname', 'userid') and returns the users. -func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { - log := appctx.GetLogger(ctx) - entries, err := m.c.LDAPIdentity.GetLDAPUsers(log, m.ldapClient, query) - if err != nil { - return nil, err + groups := []string{} + if !skipFetchingGroups { + groups, err = m.GetUserGroups(ctx, id) + if err != nil { + return nil, err + } } - users := []*userpb.User{} - for _, entry := range entries { - u, err := m.ldapEntryToUser(entry) + gidNumber := m.c.Nobody + gidValue := sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.GIDNumber) + if gidValue != "" { + gidNumber, err = strconv.ParseInt(gidValue, 10, 64) if err != nil { return nil, err } @@ -181,9 +182,23 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, return users, nil } -// GetUserGroups implements the user.Manager interface. Looks up all group membership of -// the user with the supplied Id. Returns a string slice with the group ids -func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { + // TODO align supported claims with rest driver and the others, maybe refactor into common mapping + switch claim { + case "mail": + claim = m.c.Schema.Mail + case "uid": + claim = m.c.Schema.UIDNumber + case "gid": + claim = m.c.Schema.GIDNumber + case "username": + claim = m.c.Schema.CN + case "userid": + claim = m.c.Schema.UID + default: + return nil, errors.New("ldap: invalid field " + claim) + } + log := appctx.GetLogger(ctx) if uid.Idp != "" && uid.Idp != m.c.Idp { return nil, errtypes.NotFound("idp mismatch") @@ -195,10 +210,20 @@ func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]stri return m.c.LDAPIdentity.GetLDAPUserGroups(log, m.ldapClient, userEntry) } -func (m *manager) ldapEntryToUser(entry *ldap.Entry) (*userpb.User, error) { - id, err := m.ldapEntryToUserID(entry) - if err != nil { - return nil, err + log.Debug().Interface("entries", sr.Entries).Msg("entries") + + id := &userpb.UserId{ + Idp: m.c.Idp, + OpaqueId: sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, + } + + groups := []string{} + if !skipFetchingGroups { + groups, err = m.GetUserGroups(ctx, id) + if err != nil { + return nil, err + } } gidNumber := m.c.Nobody @@ -228,17 +253,70 @@ func (m *manager) ldapEntryToUser(entry *ldap.Entry) (*userpb.User, error) { return u, nil } -func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) { - var uid string - if m.c.LDAPIdentity.User.Schema.IDIsOctetString { - rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.User.Schema.ID) - if value, err := uuid.FromBytes(rawValue); err == nil { - uid = value.String() - } else { - return nil, err +func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { + l, err := utils.GetLDAPConnection(&m.c.LDAPConn) + if err != nil { + return nil, err + } + defer l.Close() + + // Search for the given clientID + searchRequest := ldap.NewSearchRequest( + m.c.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + m.getFindFilter(query), + []string{m.c.Schema.DN, m.c.Schema.UID, m.c.Schema.CN, m.c.Schema.Mail, m.c.Schema.DisplayName, m.c.Schema.UIDNumber, m.c.Schema.GIDNumber}, + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + return nil, err + } + + users := []*userpb.User{} + + for _, entry := range sr.Entries { + id := &userpb.UserId{ + Idp: m.c.Idp, + OpaqueId: entry.GetEqualFoldAttributeValue(m.c.Schema.UID), + Type: userpb.UserType_USER_TYPE_PRIMARY, + } + + groups := []string{} + if !skipFetchingGroups { + groups, err = m.GetUserGroups(ctx, id) + if err != nil { + return nil, err + } + } + + gidNumber := m.c.Nobody + gidValue := sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.GIDNumber) + if gidValue != "" { + gidNumber, err = strconv.ParseInt(gidValue, 10, 64) + if err != nil { + return nil, err + } + } + uidNumber := m.c.Nobody + uidValue := sr.Entries[0].GetEqualFoldAttributeValue(m.c.Schema.UIDNumber) + if uidValue != "" { + uidNumber, err = strconv.ParseInt(uidValue, 10, 64) + if err != nil { + return nil, err + } + } + user := &userpb.User{ + Id: id, + Username: entry.GetEqualFoldAttributeValue(m.c.Schema.CN), + Groups: groups, + Mail: entry.GetEqualFoldAttributeValue(m.c.Schema.Mail), + DisplayName: entry.GetEqualFoldAttributeValue(m.c.Schema.DisplayName), + GidNumber: gidNumber, + UidNumber: uidNumber, } - } else { - uid = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.ID) + users = append(users, user) } return &userpb.UserId{ diff --git a/pkg/user/manager/nextcloud/nextcloud.go b/pkg/user/manager/nextcloud/nextcloud.go index b9dae3eca9..fd1d0a59f8 100644 --- a/pkg/user/manager/nextcloud/nextcloud.go +++ b/pkg/user/manager/nextcloud/nextcloud.go @@ -150,7 +150,7 @@ func (um *Manager) Configure(ml map[string]interface{}) error { } // GetUser method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 -func (um *Manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (um *Manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { bodyStr, _ := json.Marshal(uid) _, respBody, err := um.do(ctx, Action{"GetUser", string(bodyStr)}, "unauthenticated") if err != nil { @@ -165,7 +165,7 @@ func (um *Manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.Use } // GetUserByClaim method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 -func (um *Manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (um *Manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { type paramsObj struct { Claim string `json:"claim"` Value string `json:"value"` @@ -216,7 +216,7 @@ func (um *Manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]str } // FindUsers method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 -func (um *Manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (um *Manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { user, err := getUser(ctx) if err != nil { return nil, err diff --git a/pkg/user/manager/nextcloud/nextcloud_test.go b/pkg/user/manager/nextcloud/nextcloud_test.go index 6b4538d312..13408b3105 100644 --- a/pkg/user/manager/nextcloud/nextcloud_test.go +++ b/pkg/user/manager/nextcloud/nextcloud_test.go @@ -131,7 +131,8 @@ var _ = Describe("Nextcloud", func() { Idp: "some-idp", OpaqueId: "some-opaque-user-id", Type: 1, - }) + }, + false) Expect(err).ToNot(HaveOccurred()) Expect(user).To(Equal(&userpb.User{ Id: &userpb.UserId{ @@ -158,7 +159,7 @@ var _ = Describe("Nextcloud", func() { um, called, teardown := setUpNextcloudServer() defer teardown() - user, err := um.GetUserByClaim(ctx, "claim-string", "value-string") + user, err := um.GetUserByClaim(ctx, "claim-string", "value-string", false) Expect(err).ToNot(HaveOccurred()) Expect(user).To(Equal(&userpb.User{ Id: &userpb.UserId{ @@ -202,7 +203,7 @@ var _ = Describe("Nextcloud", func() { um, called, teardown := setUpNextcloudServer() defer teardown() - users, err := um.FindUsers(ctx, "some-query") + users, err := um.FindUsers(ctx, "some-query", false) Expect(err).ToNot(HaveOccurred()) Expect(len(users)).To(Equal(1)) Expect(*users[0]).To(Equal(userpb.User{ diff --git a/pkg/user/manager/owncloudsql/owncloudsql.go b/pkg/user/manager/owncloudsql/owncloudsql.go index 128474004c..b1c595f3e4 100644 --- a/pkg/user/manager/owncloudsql/owncloudsql.go +++ b/pkg/user/manager/owncloudsql/owncloudsql.go @@ -102,26 +102,26 @@ func parseConfig(m map[string]interface{}) (*config, error) { return c, nil } -func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { // search via the user_id a, err := m.db.GetAccountByClaim(ctx, "userid", uid.OpaqueId) if err == sql.ErrNoRows { return nil, errtypes.NotFound(uid.OpaqueId) } - return m.convertToCS3User(ctx, a) + return m.convertToCS3User(ctx, a, skipFetchingGroups) } -func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { a, err := m.db.GetAccountByClaim(ctx, claim, value) if err == sql.ErrNoRows { return nil, errtypes.NotFound(claim + "=" + value) } else if err != nil { return nil, err } - return m.convertToCS3User(ctx, a) + return m.convertToCS3User(ctx, a, skipFetchingGroups) } -func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *manager) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { accounts, err := m.db.FindAccounts(ctx, query) if err == sql.ErrNoRows { @@ -132,7 +132,7 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, users := make([]*userpb.User, 0, len(accounts)) for i := range accounts { - u, err := m.convertToCS3User(ctx, &accounts[i]) + u, err := m.convertToCS3User(ctx, &accounts[i], skipFetchingGroups) if err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("account", accounts[i]).Msg("could not convert account, skipping") continue @@ -153,7 +153,7 @@ func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]stri return groups, nil } -func (m *manager) convertToCS3User(ctx context.Context, a *accounts.Account) (*userpb.User, error) { +func (m *manager) convertToCS3User(ctx context.Context, a *accounts.Account, skipFetchingGroups bool) (*userpb.User, error) { u := &userpb.User{ Id: &userpb.UserId{ Idp: m.c.Idp, @@ -172,9 +172,12 @@ func (m *manager) convertToCS3User(ctx context.Context, a *accounts.Account) (*u if u.DisplayName == "" { u.DisplayName = u.Id.OpaqueId } - var err error - if u.Groups, err = m.GetUserGroups(ctx, u.Id); err != nil { - return nil, err + + if !skipFetchingGroups { + var err error + if u.Groups, err = m.GetUserGroups(ctx, u.Id); err != nil { + return nil, err + } } return u, nil } diff --git a/pkg/user/rpc_user.go b/pkg/user/rpc_user.go index d9f9844ae4..fd6d35c763 100644 --- a/pkg/user/rpc_user.go +++ b/pkg/user/rpc_user.go @@ -75,8 +75,9 @@ func (m *RPCClient) Configure(ml map[string]interface{}) error { // GetUserArg for RPC type GetUserArg struct { - Ctx map[interface{}]interface{} - UID *userpb.UserId + Ctx map[interface{}]interface{} + UID *userpb.UserId + SkipFetchingGroups bool } // GetUserReply for RPC @@ -86,9 +87,9 @@ type GetUserReply struct { } // GetUser RPCClient GetUser method -func (m *RPCClient) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { +func (m *RPCClient) GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) { ctxVal := appctx.GetKeyValuesFromCtx(ctx) - args := GetUserArg{Ctx: ctxVal, UID: uid} + args := GetUserArg{Ctx: ctxVal, UID: uid, SkipFetchingGroups: skipFetchingGroups} resp := GetUserReply{} err := m.Client.Call("Plugin.GetUser", args, &resp) if err != nil { @@ -99,9 +100,10 @@ func (m *RPCClient) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.Us // GetUserByClaimArg for RPC type GetUserByClaimArg struct { - Ctx map[interface{}]interface{} - Claim string - Value string + Ctx map[interface{}]interface{} + Claim string + Value string + SkipFetchingGroups bool } // GetUserByClaimReply for RPC @@ -111,9 +113,9 @@ type GetUserByClaimReply struct { } // GetUserByClaim RPCClient GetUserByClaim method -func (m *RPCClient) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { +func (m *RPCClient) GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) { ctxVal := appctx.GetKeyValuesFromCtx(ctx) - args := GetUserByClaimArg{Ctx: ctxVal, Claim: claim, Value: value} + args := GetUserByClaimArg{Ctx: ctxVal, Claim: claim, Value: value, SkipFetchingGroups: skipFetchingGroups} resp := GetUserByClaimReply{} err := m.Client.Call("Plugin.GetUserByClaim", args, &resp) if err != nil { @@ -148,8 +150,9 @@ func (m *RPCClient) GetUserGroups(ctx context.Context, user *userpb.UserId) ([]s // FindUsersArg for RPC type FindUsersArg struct { - Ctx map[interface{}]interface{} - Query string + Ctx map[interface{}]interface{} + Query string + SkipFetchingGroups bool } // FindUsersReply for RPC @@ -159,9 +162,9 @@ type FindUsersReply struct { } // FindUsers RPCClient FindUsers method -func (m *RPCClient) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { +func (m *RPCClient) FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) { ctxVal := appctx.GetKeyValuesFromCtx(ctx) - args := FindUsersArg{Ctx: ctxVal, Query: query} + args := FindUsersArg{Ctx: ctxVal, Query: query, SkipFetchingGroups: skipFetchingGroups} resp := FindUsersReply{} err := m.Client.Call("Plugin.FindUsers", args, &resp) if err != nil { @@ -185,14 +188,14 @@ func (m *RPCServer) Configure(args ConfigureArg, resp *ConfigureReply) error { // GetUser RPCServer GetUser method func (m *RPCServer) GetUser(args GetUserArg, resp *GetUserReply) error { ctx := appctx.PutKeyValuesToCtx(args.Ctx) - resp.User, resp.Err = m.Impl.GetUser(ctx, args.UID) + resp.User, resp.Err = m.Impl.GetUser(ctx, args.UID, args.SkipFetchingGroups) return nil } // GetUserByClaim RPCServer GetUserByClaim method func (m *RPCServer) GetUserByClaim(args GetUserByClaimArg, resp *GetUserByClaimReply) error { ctx := appctx.PutKeyValuesToCtx(args.Ctx) - resp.User, resp.Err = m.Impl.GetUserByClaim(ctx, args.Claim, args.Value) + resp.User, resp.Err = m.Impl.GetUserByClaim(ctx, args.Claim, args.Value, args.SkipFetchingGroups) return nil } @@ -206,6 +209,6 @@ func (m *RPCServer) GetUserGroups(args GetUserGroupsArg, resp *GetUserGroupsRepl // FindUsers RPCServer FindUsers method func (m *RPCServer) FindUsers(args FindUsersArg, resp *FindUsersReply) error { ctx := appctx.PutKeyValuesToCtx(args.Ctx) - resp.User, resp.Err = m.Impl.FindUsers(ctx, args.Query) + resp.User, resp.Err = m.Impl.FindUsers(ctx, args.Query, args.SkipFetchingGroups) return nil } diff --git a/pkg/user/user.go b/pkg/user/user.go index 55339d741a..3ee2537f6d 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -28,8 +28,14 @@ import ( // Manager is the interface to implement to manipulate users. type Manager interface { plugin.Plugin - GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) - GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) + // GetUser returns the user metadata identified by a uid. + // The groups of the user are omitted if specified, as these might not be required for certain operations + // and might involve computational overhead. + GetUser(ctx context.Context, uid *userpb.UserId, skipFetchingGroups bool) (*userpb.User, error) + // GetUserByClaim returns the user identified by a specific value for a given claim. + GetUserByClaim(ctx context.Context, claim, value string, skipFetchingGroups bool) (*userpb.User, error) + // GetUserGroups returns the groups a user identified by a uid belongs to. GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) - FindUsers(ctx context.Context, query string) ([]*userpb.User, error) + // FindUsers returns all the user objects which match a query parameter. + FindUsers(ctx context.Context, query string, skipFetchingGroups bool) ([]*userpb.User, error) } diff --git a/pkg/utils/resourceid/owncloud_test.go b/pkg/utils/resourceid/owncloud_test.go index e87e4bea4f..d23574ef58 100644 --- a/pkg/utils/resourceid/owncloud_test.go +++ b/pkg/utils/resourceid/owncloud_test.go @@ -21,7 +21,7 @@ import ( "testing" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/pkg/utils" ) func BenchmarkWrap(b *testing.B) { diff --git a/tests/acceptance/expected-failures-on-EOS-storage.md b/tests/acceptance/expected-failures-on-EOS-storage.md index 30a78fd5a2..e50eb5e1fe 100644 --- a/tests/acceptance/expected-failures-on-EOS-storage.md +++ b/tests/acceptance/expected-failures-on-EOS-storage.md @@ -71,6 +71,7 @@ ### [HTTP 401 Unauthorized responses don't contain a body](https://github.com/owncloud/ocis/issues/1337) - [apiAuthOcs/ocsDELETEAuth.feature:9](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L9) - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) +- [apiAuthOcs/ocsGETAuth.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L33) - [apiAuthOcs/ocsGETAuth.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L53) - [apiAuthOcs/ocsGETAuth.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L88) - [apiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L121) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index ca6d7558f4..6d6acf599d 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -5,12 +5,12 @@ Basic file management like up and download, move, copy, properties, quota, trash #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) These tests succeed when running against ocis because there we handle the relevant authentication in the proxy. -- [apiTrashbin/trashbinFilesFolders.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L208) -- [apiTrashbin/trashbinFilesFolders.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L209) -- [apiTrashbin/trashbinFilesFolders.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L223) -- [apiTrashbin/trashbinFilesFolders.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L224) -- [apiTrashbin/trashbinFilesFolders.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L238) -- [apiTrashbin/trashbinFilesFolders.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L239) +- [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) +- [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) +- [apiTrashbin/trashbinFilesFolders.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L215) +- [apiTrashbin/trashbinFilesFolders.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L216) +- [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) +- [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) These tests are about overwriting files or folders in the `Shares` folder of a user. @@ -18,124 +18,105 @@ These tests are about overwriting files or folders in the `Shares` folder of a u - [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) - [apiWebdavProperties1/copyFile.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L309) - [apiWebdavProperties1/copyFile.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L310) +- [apiWebdavProperties1/copyFile.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L337) +- [apiWebdavProperties1/copyFile.feature:338](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L338) +- [apiWebdavProperties1/copyFile.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L367) +- [apiWebdavProperties1/copyFile.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L368) +- [apiWebdavProperties1/copyFile.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L396) +- [apiWebdavProperties1/copyFile.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L397) +- [apiWebdavProperties1/copyFile.feature:425](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L425) +- [apiWebdavProperties1/copyFile.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L426) +- [apiWebdavProperties1/copyFile.feature:509](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L509) +- [apiWebdavProperties1/copyFile.feature:542](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L542) +- [apiWebdavProperties1/copyFile.feature:543](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L543) +- [apiWebdavProperties1/copyFile.feature:574](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L574) +- [apiWebdavProperties1/copyFile.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L575) +- [apiWebdavProperties1/copyFile.feature:606](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L606) #### [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) #### [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:401](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L401) - [apiWebdavProperties2/getFileProperties.feature:406](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L406) -- [apiWebdavProperties2/getFileProperties.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L411) ### Sync 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:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L381) -- [apiMain/checksums.feature:386](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L386) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) +- [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) +- [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) +- [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) +- [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) - [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) - [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) - [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) - [apiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L46) -- [apiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L51) -- [apiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L52) - [apiWebdavLocks/exclusiveLocks.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L69) - [apiWebdavLocks/exclusiveLocks.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L70) - [apiWebdavLocks/exclusiveLocks.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L71) - [apiWebdavLocks/exclusiveLocks.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L72) -- [apiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L77) -- [apiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L78) - [apiWebdavLocks/exclusiveLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L94) - [apiWebdavLocks/exclusiveLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L95) - [apiWebdavLocks/exclusiveLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L96) - [apiWebdavLocks/exclusiveLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L97) -- [apiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L102) -- [apiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L103) - [apiWebdavLocks/exclusiveLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L120) - [apiWebdavLocks/exclusiveLocks.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L121) - [apiWebdavLocks/exclusiveLocks.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L122) - [apiWebdavLocks/exclusiveLocks.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L123) -- [apiWebdavLocks/exclusiveLocks.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L128) -- [apiWebdavLocks/exclusiveLocks.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L129) - [apiWebdavLocks/exclusiveLocks.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L147) - [apiWebdavLocks/exclusiveLocks.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L148) - [apiWebdavLocks/exclusiveLocks.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L149) - [apiWebdavLocks/exclusiveLocks.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L150) -- [apiWebdavLocks/exclusiveLocks.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L155) -- [apiWebdavLocks/exclusiveLocks.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L156) - [apiWebdavLocks/exclusiveLocks.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L174) - [apiWebdavLocks/exclusiveLocks.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L175) - [apiWebdavLocks/exclusiveLocks.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L176) - [apiWebdavLocks/exclusiveLocks.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L177) -- [apiWebdavLocks/exclusiveLocks.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L182) -- [apiWebdavLocks/exclusiveLocks.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L183) - [apiWebdavLocks/exclusiveLocks.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L201) - [apiWebdavLocks/exclusiveLocks.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L202) - [apiWebdavLocks/exclusiveLocks.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L203) - [apiWebdavLocks/exclusiveLocks.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L204) -- [apiWebdavLocks/exclusiveLocks.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L209) -- [apiWebdavLocks/exclusiveLocks.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L210) - [apiWebdavLocks/folder.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L18) - [apiWebdavLocks/folder.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L19) - [apiWebdavLocks/folder.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L20) - [apiWebdavLocks/folder.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L21) -- [apiWebdavLocks/folder.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L26) -- [apiWebdavLocks/folder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L27) - [apiWebdavLocks/folder.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L41) - [apiWebdavLocks/folder.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L42) - [apiWebdavLocks/folder.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L43) - [apiWebdavLocks/folder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L44) -- [apiWebdavLocks/folder.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L49) -- [apiWebdavLocks/folder.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L50) - [apiWebdavLocks/folder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L63) - [apiWebdavLocks/folder.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L64) - [apiWebdavLocks/folder.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L65) - [apiWebdavLocks/folder.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L66) -- [apiWebdavLocks/folder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L71) -- [apiWebdavLocks/folder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L72) - [apiWebdavLocks/folder.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L86) - [apiWebdavLocks/folder.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L87) - [apiWebdavLocks/folder.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L88) - [apiWebdavLocks/folder.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L89) -- [apiWebdavLocks/folder.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L94) -- [apiWebdavLocks/folder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L95) - [apiWebdavLocks/folder.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L110) - [apiWebdavLocks/folder.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L111) - [apiWebdavLocks/folder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L112) - [apiWebdavLocks/folder.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L113) -- [apiWebdavLocks/folder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L118) -- [apiWebdavLocks/folder.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L119) - [apiWebdavLocks/folder.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L135) - [apiWebdavLocks/folder.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L136) - [apiWebdavLocks/folder.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L137) - [apiWebdavLocks/folder.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L138) -- [apiWebdavLocks/folder.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L144) -- [apiWebdavLocks/folder.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L143) - [apiWebdavLocks/folder.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L161) - [apiWebdavLocks/folder.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L162) - [apiWebdavLocks/folder.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L163) - [apiWebdavLocks/folder.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L164) -- [apiWebdavLocks/folder.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L169) -- [apiWebdavLocks/folder.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L170) - [apiWebdavLocks/publicLink.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L33) - [apiWebdavLocks/publicLink.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L34) - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) - [apiWebdavLocks/publicLink.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L36) -- [apiWebdavLocks/publicLink.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L41) -- [apiWebdavLocks/publicLink.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L42) -- [apiWebdavLocks/publicLink.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L43) -- [apiWebdavLocks/publicLink.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L44) - [apiWebdavLocks/publicLink.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L60) - [apiWebdavLocks/publicLink.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L61) - [apiWebdavLocks/publicLink.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) @@ -162,19 +143,14 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L74) - [apiWebdavLocks/requestsWithToken.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L75) - [apiWebdavLocks/requestsWithToken.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L76) -- [apiWebdavLocks/requestsWithToken.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L81) -- [apiWebdavLocks/requestsWithToken.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L82) - [apiWebdavLocks/requestsWithToken.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L100) - [apiWebdavLocks/requestsWithToken.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L101) - [apiWebdavLocks/requestsWithToken.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L102) - [apiWebdavLocks/requestsWithToken.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L103) - [apiWebdavLocks/requestsWithToken.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L131) - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) -- [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) -- [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) - [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L156) - [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L157) -- [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L31) - [apiWebdavLocks2/resharedSharesToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L32) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) @@ -199,8 +175,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L25) - [apiWebdavLocks2/setTimeout.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L26) - [apiWebdavLocks2/setTimeout.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L27) -- [apiWebdavLocks2/setTimeout.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L32) -- [apiWebdavLocks2/setTimeout.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L33) - [apiWebdavLocks2/setTimeout.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L47) - [apiWebdavLocks2/setTimeout.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L48) - [apiWebdavLocks2/setTimeout.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L49) @@ -211,11 +185,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L54) - [apiWebdavLocks2/setTimeout.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L55) - [apiWebdavLocks2/setTimeout.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L56) -- [apiWebdavLocks2/setTimeout.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L61) -- [apiWebdavLocks2/setTimeout.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L62) -- [apiWebdavLocks2/setTimeout.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L63) -- [apiWebdavLocks2/setTimeout.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L64) -- [apiWebdavLocks2/setTimeout.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L65) - [apiWebdavLocks2/setTimeout.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L81) - [apiWebdavLocks2/setTimeout.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L82) - [apiWebdavLocks2/setTimeout.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L83) @@ -228,12 +197,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L90) - [apiWebdavLocks2/setTimeout.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L91) - [apiWebdavLocks2/setTimeout.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L92) -- [apiWebdavLocks2/setTimeout.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L97) -- [apiWebdavLocks2/setTimeout.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L98) -- [apiWebdavLocks2/setTimeout.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L99) -- [apiWebdavLocks2/setTimeout.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L100) -- [apiWebdavLocks2/setTimeout.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L101) -- [apiWebdavLocks2/setTimeout.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L102) - [apiWebdavLocks2/setTimeout.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L117) - [apiWebdavLocks2/setTimeout.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L118) - [apiWebdavLocks2/setTimeout.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L119) @@ -268,20 +231,14 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks3/independentLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L26) - [apiWebdavLocks3/independentLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L27) - [apiWebdavLocks3/independentLocks.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L28) -- [apiWebdavLocks3/independentLocks.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L33) -- [apiWebdavLocks3/independentLocks.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L34) - [apiWebdavLocks3/independentLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L51) - [apiWebdavLocks3/independentLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L52) - [apiWebdavLocks3/independentLocks.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L53) - [apiWebdavLocks3/independentLocks.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L54) -- [apiWebdavLocks3/independentLocks.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L59) -- [apiWebdavLocks3/independentLocks.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L60) - [apiWebdavLocks3/independentLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L77) - [apiWebdavLocks3/independentLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L78) - [apiWebdavLocks3/independentLocks.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L79) - [apiWebdavLocks3/independentLocks.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L80) -- [apiWebdavLocks3/independentLocks.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L85) -- [apiWebdavLocks3/independentLocks.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L86) - [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) @@ -290,101 +247,70 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks3/independentLocks.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L110) - [apiWebdavLocks3/independentLocks.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L111) - [apiWebdavLocks3/independentLocks.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L112) -- [apiWebdavLocks3/independentLocks.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L117) -- [apiWebdavLocks3/independentLocks.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L118) -- [apiWebdavLocks3/independentLocks.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L119) -- [apiWebdavLocks3/independentLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L120) - [apiWebdavLocks3/independentLocksShareToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L28) - [apiWebdavLocks3/independentLocksShareToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L29) - [apiWebdavLocks3/independentLocksShareToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L30) - [apiWebdavLocks3/independentLocksShareToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L31) -- [apiWebdavLocks3/independentLocksShareToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L36) -- [apiWebdavLocks3/independentLocksShareToShares.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L37) - [apiWebdavLocks3/independentLocksShareToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L57) - [apiWebdavLocks3/independentLocksShareToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L58) - [apiWebdavLocks3/independentLocksShareToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L59) - [apiWebdavLocks3/independentLocksShareToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L60) -- [apiWebdavLocks3/independentLocksShareToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L65) -- [apiWebdavLocks3/independentLocksShareToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L66) - [apiWebdavLocks3/independentLocksShareToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L87) - [apiWebdavLocks3/independentLocksShareToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L88) - [apiWebdavLocks3/independentLocksShareToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L89) - [apiWebdavLocks3/independentLocksShareToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L90) -- [apiWebdavLocks3/independentLocksShareToShares.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L95) -- [apiWebdavLocks3/independentLocksShareToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L96) - [apiWebdavLocks3/independentLocksShareToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L116) - [apiWebdavLocks3/independentLocksShareToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L117) - [apiWebdavLocks3/independentLocksShareToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L118) - [apiWebdavLocks3/independentLocksShareToShares.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L119) -- [apiWebdavLocks3/independentLocksShareToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L124) -- [apiWebdavLocks3/independentLocksShareToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L125) -- [apiWebdavLocksUnlock/unlock.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L30) -- [apiWebdavLocksUnlock/unlock.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L31) +- [apiWebdavLocksUnlock/unlock.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L22) +- [apiWebdavLocksUnlock/unlock.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L23) +- [apiWebdavLocksUnlock/unlock.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L24) +- [apiWebdavLocksUnlock/unlock.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L25) - [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) - [apiWebdavLocksUnlock/unlock.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L47) -- [apiWebdavLocksUnlock/unlock.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L52) - [apiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L68) - [apiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L69) - [apiWebdavLocksUnlock/unlock.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L70) - [apiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L71) -- [apiWebdavLocksUnlock/unlock.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L76) -- [apiWebdavLocksUnlock/unlock.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L77) - [apiWebdavLocksUnlock/unlock.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L91) - [apiWebdavLocksUnlock/unlock.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L92) - [apiWebdavLocksUnlock/unlock.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L116) - [apiWebdavLocksUnlock/unlock.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L117) - [apiWebdavLocksUnlock/unlock.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L118) - [apiWebdavLocksUnlock/unlock.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L119) -- [apiWebdavLocksUnlock/unlock.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L124) -- [apiWebdavLocksUnlock/unlock.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L125) - [apiWebdavLocksUnlock/unlock.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L147) - [apiWebdavLocksUnlock/unlock.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L148) - [apiWebdavLocksUnlock/unlock.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L149) - [apiWebdavLocksUnlock/unlock.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L150) -- [apiWebdavLocksUnlock/unlock.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L155) -- [apiWebdavLocksUnlock/unlock.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L156) - [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:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L44) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L45) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L61) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L62) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L63) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L68) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L69) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L90) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L91) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L92) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L93) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L98) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L115) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L116) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L117) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L118) -- [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: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:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L151) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L164) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L165) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L180) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L181) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L182) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L183) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L188) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L189) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L210) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L211) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L212) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L213) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L218) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L219) ### Share File and sync features in a shared scenario @@ -412,12 +338,20 @@ File and sync features in a shared scenario #### [cannot accept identical pending shares from different user serially](https://github.com/owncloud/ocis/issues/2131) - [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) - [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L162) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L163) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L202) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L203) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L45) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L46) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) + +#### [Shares received in different ways are not merged](https://github.com/owncloud/ocis/issues/2711) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:582](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L582) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:583](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L583) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L608) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:609](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L609) #### [sharing with group not available](https://github.com/owncloud/product/issues/293) The first two tests work against ocis. There must be something wrong in the CI setup. @@ -468,10 +402,28 @@ The first two tests work against ocis. There must be something wrong in the CI s - [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) +#### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) + +- [apiSharePublicLink1/createPublicLinkShare.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L141) +- [apiSharePublicLink1/createPublicLinkShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L142) +- [apiSharePublicLink1/createPublicLinkShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L218) +- [apiSharePublicLink1/createPublicLinkShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L219) + +#### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) + +- [apiSharePublicLink1/createPublicLinkShare.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L105) +- [apiSharePublicLink1/createPublicLinkShare.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L106) + #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiSharePublicLink1/createPublicLinkShare.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L375) - [apiSharePublicLink1/createPublicLinkShare.feature:376](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L376) +- [apiSharePublicLink1/createPublicLinkShare.feature:477](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L477) +- [apiSharePublicLink1/createPublicLinkShare.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L478) +- [apiSharePublicLink1/createPublicLinkShare.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L566) +- [apiSharePublicLink1/createPublicLinkShare.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L567) +- [apiSharePublicLink1/createPublicLinkShare.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L587) +- [apiSharePublicLink1/createPublicLinkShare.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L608) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L212) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L213) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L214) @@ -483,11 +435,6 @@ The first two tests work against ocis. There must be something wrong in the CI s - [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L104) - [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L105) -#### [Public cannot upload file with mtime set on a public link share with new version of WebDAV API](https://github.com/owncloud/core/issues/37605) - -- [apiSharePublicLink1/createPublicLinkShare.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L587) -- [apiSharePublicLink1/createPublicLinkShare.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L608) - #### [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:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L59) @@ -567,8 +514,9 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares3/reShareUpdate.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L62) #### [path property in pending shares gives only filename](https://github.com/owncloud/ocis/issues/2156) -- [apiShareReshareToShares2/reShareSubfolder.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L178) -- [apiShareReshareToShares2/reShareSubfolder.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L179) +- [apiShareReshareToShares2/reShareSubfolder.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L180) +- [apiShareReshareToShares2/reShareSubfolder.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L181) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L59) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:735](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L735) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:736](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L736) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:754](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L754) @@ -576,20 +524,29 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:770](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L770) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:771](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L771) +#### [Listing shares via ocs API does not show path for parent folders](https://github.com/owncloud/ocis/issues/1231) + +- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:290](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L290) +- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L291) + #### [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:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L42) -- [apiTrashbin/trashbinSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L43) -- [apiTrashbin/trashbinSharingToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L65) -- [apiTrashbin/trashbinSharingToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L66) -- [apiTrashbin/trashbinSharingToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L88) -- [apiTrashbin/trashbinSharingToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L89) -- [apiTrashbin/trashbinSharingToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L112) -- [apiTrashbin/trashbinSharingToShares.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L113) -- [apiTrashbin/trashbinSharingToShares.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L136) -- [apiTrashbin/trashbinSharingToShares.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L137) -- [apiTrashbin/trashbinSharingToShares.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L160) -- [apiTrashbin/trashbinSharingToShares.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L161) +- [apiTrashbin/trashbinSharingToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L40) +- [apiTrashbin/trashbinSharingToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L41) +- [apiTrashbin/trashbinSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L62) +- [apiTrashbin/trashbinSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L63) +- [apiTrashbin/trashbinSharingToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L84) +- [apiTrashbin/trashbinSharingToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L85) +- [apiTrashbin/trashbinSharingToShares.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L107) +- [apiTrashbin/trashbinSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L108) +- [apiTrashbin/trashbinSharingToShares.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L130) +- [apiTrashbin/trashbinSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L131) +- [apiTrashbin/trashbinSharingToShares.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L154) +- [apiTrashbin/trashbinSharingToShares.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L155) + +#### [Folder overwrite on shared files doesn't works correctly on copying file](https://github.com/owncloud/ocis/issues/2183) +- [apiWebdavProperties1/copyFile.feature:510](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L510) +- [apiWebdavProperties1/copyFile.feature:607](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L607) #### [changing user quota gives ocs status 103 / cannot set user quota using the ocs endpoint](https://github.com/owncloud/product/issues/247) _getting and setting quota_ @@ -615,63 +572,69 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) - [apiWebdavProperties1/getQuota.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L19) -- [apiWebdavProperties1/getQuota.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L24) Scenario Outline: Retrieving folder quota when quota is set - [apiWebdavProperties1/getQuota.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L34) - [apiWebdavProperties1/getQuota.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L35) -- [apiWebdavProperties1/getQuota.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L40) Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient - [apiWebdavProperties1/getQuota.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L61) - [apiWebdavProperties1/getQuota.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L62) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded -- [apiWebdavProperties1/getQuota.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L67) - Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded - [apiWebdavProperties1/getQuota.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L81) - [apiWebdavProperties1/getQuota.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L82) - Scenario Outline: Retrieving folder quota when quota is set and a file was received -- [apiWebdavProperties1/getQuota.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L87) Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L103) - [apiWebdavProperties1/getQuota.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L104) -- [apiWebdavProperties1/getQuota.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L109) + +#### [cannot get share-types webdav property](https://github.com/owncloud/ocis/issues/567) +- [apiWebdavProperties2/getFileProperties.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L238) +- [apiWebdavProperties2/getFileProperties.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L239) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) - [apiWebdavProperties2/getFileProperties.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L306) - [apiWebdavProperties2/getFileProperties.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L307) -- [apiWebdavProperties2/getFileProperties.feature:312](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L312) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ - [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) - [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) -- [apiShareOperationsToShares2/uploadToShare.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L199) - [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) - [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) -- [apiShareOperationsToShares2/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L224) - [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) -- [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) - [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) - [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) -- [apiShareOperationsToShares2/uploadToShare.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L276) - [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) - [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) -- [apiShareOperationsToShares2/uploadToShare.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L303) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L30) - [apiShareOperationsToShares1/changingFilesShare.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L115) - [apiShareOperationsToShares1/changingFilesShare.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L116) -- [apiShareOperationsToShares1/changingFilesShare.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L121) - [apiShareOperationsToShares1/changingFilesShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L142) - [apiShareOperationsToShares1/changingFilesShare.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L143) -- [apiShareOperationsToShares1/changingFilesShare.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L148) +- [apiShareOperationsToShares1/changingFilesShare.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L163) +- [apiShareOperationsToShares1/changingFilesShare.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L164) +- [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:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L60) +- [apiWebdavMove2/moveShareOnOcis.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L62) +- [apiWebdavMove2/moveShareOnOcis.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L91) +- [apiWebdavMove2/moveShareOnOcis.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L93) +- [apiWebdavMove2/moveShareOnOcis.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L124) +- [apiWebdavMove2/moveShareOnOcis.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L126) +- [apiWebdavMove2/moveShareOnOcis.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L152) +- [apiWebdavMove2/moveShareOnOcis.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L153) +- [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) + +#### [OCIS-storage overwriting a file as share receiver, does not create a new file version for the sharer](https://github.com/owncloud/ocis/issues/766) +- [apiVersions/fileVersionsSharingToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L32) #### [restoring an older version of a shared file deletes the share](https://github.com/owncloud/ocis/issues/765) - [apiShareManagementToShares/acceptShares.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L587) +- [apiVersions/fileVersionsSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L43) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) @@ -699,40 +662,37 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt #### [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:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L29) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L30) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L58) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L59) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L86) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L87) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L113) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L114) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L140) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L141) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L162) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L163) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L303) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:304](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L304) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L325) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L326) - [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:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L348) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L370) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L371) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:388](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L388) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L389) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:406](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L406) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:407](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L407) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L566) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L567) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:584](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L584) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:585](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L585) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:606](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L606) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:607](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L607) - [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) @@ -744,36 +704,66 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [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) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:662](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L662) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:663](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L663) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:664](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L664) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:665](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L665) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L666) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:667](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L667) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L688) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:689](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L689) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:690](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L690) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:691](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L691) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:692](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L692) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:693](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L693) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:714](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L714) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:715](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L715) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:716](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L716) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:717](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L717) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L718) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:719](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L719) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:740](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L740) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:741](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L741) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:762](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L762) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:763](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L763) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:784](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L784) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:785](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L785) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L36) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L37) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L92) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L93) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L94) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L95) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L124) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L125) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L126) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L127) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L153) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L154) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L155) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L156) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:186](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L186) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L187) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L215) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L216) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L217) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L218) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L273) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L274) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L275) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L276) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L305) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L306) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L307) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:308](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L308) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:338](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L338) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:339](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L339) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:340](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L340) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L341) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L368) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L369) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L370) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L371) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L403) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:404](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L404) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:405](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L405) @@ -788,26 +778,26 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares3/reShareWithExpiryDate.feature:469](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L469) #### 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:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L193) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L194) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L223) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L224) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:252](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L252) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L253) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L282) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L283) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:429](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L429) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L430) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L453) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L454) - [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) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L478) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L502) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L503) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:524](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L524) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:525](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L525) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:546](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L546) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:547](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L547) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L64) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L65) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L124) @@ -820,13 +810,11 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares3/reShareWithExpiryDate.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L251) #### Expiration date of public link shares -- [apiSharePublicLink1/createPublicLinkShare.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L566) -- [apiSharePublicLink1/createPublicLinkShare.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L567) #### [incorrect ocs(v2) status value when sharing to group that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) _ocs: api compatibility, return correct status code_ -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L229) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L230) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L49) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L50) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L51) @@ -844,39 +832,40 @@ _ocs: api compatibility, return correct status code_ - [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) - [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) +- [apiShareUpdateToShares/updateShare.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L130) - [apiShareUpdateToShares/updateShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L131) - [apiShareUpdateToShares/updateShare.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L132) - [apiShareUpdateToShares/updateShare.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L133) - [apiShareUpdateToShares/updateShare.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L134) - [apiShareUpdateToShares/updateShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L135) -- [apiShareUpdateToShares/updateShare.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L136) +- [apiShareUpdateToShares/updateShare.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L154) - [apiShareUpdateToShares/updateShare.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L155) - [apiShareUpdateToShares/updateShare.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L156) - [apiShareUpdateToShares/updateShare.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L157) - [apiShareUpdateToShares/updateShare.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L158) - [apiShareUpdateToShares/updateShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L159) -- [apiShareUpdateToShares/updateShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L160) #### [Group shares support ](https://github.com/owncloud/ocis/issues/1289) - [apiShareOperationsToShares1/gettingShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L188) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) +- [apiShareUpdateToShares/updateShare.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L324) - [apiShareUpdateToShares/updateShare.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L325) -- [apiShareUpdateToShares/updateShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L326) - [apiShareUpdateToShares/updateShare.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L350) - [apiShareUpdateToShares/updateShare.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L351) -- [apiShareUpdateToShares/updateShare.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L369) -- [apiShareUpdateToShares/updateShare.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L370) +- [apiShareUpdateToShares/updateShare.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L367) +- [apiShareUpdateToShares/updateShare.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L368) - [apiShareUpdateToShares/updateShare.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L396) - [apiShareUpdateToShares/updateShare.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L397) -- [apiShareUpdateToShares/updateShare.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L426) - [apiShareUpdateToShares/updateShare.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L427) +- [apiShareUpdateToShares/updateShare.feature:428](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L428) + #### [Share additional info](https://github.com/owncloud/ocis/issues/1253) #### [Share extra attributes](https://github.com/owncloud/ocis/issues/1224) #### [Edit user share response has an "name" field](https://github.com/owncloud/ocis/issues/1225) +- [apiShareUpdateToShares/updateShare.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L287) - [apiShareUpdateToShares/updateShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L288) -- [apiShareUpdateToShares/updateShare.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L289) #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) - [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) @@ -885,6 +874,9 @@ _ocs: api compatibility, return correct status code_ - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:670](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L670) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:671](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L671) +#### [OCIS-storage overwriting a file as share receiver, does not create a new file version for the sharer](https://github.com/owncloud/ocis/issues/766) +- [apiVersions/fileVersionsSharingToShares.feature:294](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L294) + #### [deleting a share with wrong authentication returns OCS status 996 / HTTP 500](https://github.com/owncloud/ocis/issues/1229) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:250](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L250) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L251) @@ -901,6 +893,7 @@ 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:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously +- [apiAuthOcs/ocsGETAuth.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L31) Scenario: ocs config end point accessible by unauthorized users - [apiAuthOcs/ocsGETAuth.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L51) Scenario: using OCS with non-admin basic auth - [apiAuthOcs/ocsGETAuth.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L84) Scenario: using OCS as normal user with wrong password - [apiAuthOcs/ocsGETAuth.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L115) Scenario:using OCS with admin basic auth @@ -910,31 +903,26 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ +- [apiAuthWebDav/webDavDELETEAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L58) Scenario: send DELETE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPROPFINDAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPROPFINDAuth.feature#L57) Scenario: send PROPFIND requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPROPPATCHAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPROPPATCHAuth.feature#L58) Scenario: send PROPPATCH requests to another user's webDav endpoints as normal user - [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMKCOLAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L68) Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) - [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavLOCKAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L70) Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMOVEAuth.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L66) Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [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:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavPOSTAuth.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L67) Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [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:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) - [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) -- [apiAuthWebDav/webDavSpecialURLs.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L45) - [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: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) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -943,149 +931,51 @@ _ocdav: api compatibility, return correct status code_ #### [REPORT request not implemented](https://github.com/owncloud/ocis/issues/1330) - [apiWebdavOperations/search.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L42) - [apiWebdavOperations/search.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L43) -- [apiWebdavOperations/search.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L48) - [apiWebdavOperations/search.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L64) - [apiWebdavOperations/search.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L65) -- [apiWebdavOperations/search.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L70) - [apiWebdavOperations/search.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L87) - [apiWebdavOperations/search.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L88) -- [apiWebdavOperations/search.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L93) - [apiWebdavOperations/search.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L102) - [apiWebdavOperations/search.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L103) -- [apiWebdavOperations/search.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L108) - [apiWebdavOperations/search.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L126) - [apiWebdavOperations/search.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L127) -- [apiWebdavOperations/search.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L132) - [apiWebdavOperations/search.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L150) - [apiWebdavOperations/search.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L151) -- [apiWebdavOperations/search.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L156) - [apiWebdavOperations/search.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L174) - [apiWebdavOperations/search.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L175) -- [apiWebdavOperations/search.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L180) - [apiWebdavOperations/search.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L207) - [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) -- [apiWebdavOperations/search.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L213) - [apiWebdavOperations/search.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L239) - [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) -- [apiWebdavOperations/search.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L245) - [apiWebdavOperations/search.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L264) - [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) -- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) And other missing implementation of favorites - [apiFavorites/favorites.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L162) - [apiFavorites/favorites.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L163) - [apiFavorites/favorites.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L187) - [apiFavorites/favorites.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L188) -- [apiFavorites/favorites.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L193) - [apiFavorites/favorites.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L220) - [apiFavorites/favorites.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L221) -- [apiFavorites/favorites.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L226) - [apiFavorites/favoritesSharingToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L82) -- [apiFavorites/favoritesSharingToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L88) +- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) #### [resource inside Shares dir is not found using the spaces WebDAV API](https://github.com/owncloud/ocis/issues/2968) -- [apiFavorites/favorites.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L168) -- [apiFavorites/favoritesSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L28) -- [apiFavorites/favoritesSharingToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L48) -- [apiFavorites/favoritesSharingToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L67) -- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) -- [apiFavorites/favoritesSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L108) -- [apiMain/checksums.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L211) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L49) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L75) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L94) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L120) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L139) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L165) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L203) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L228) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:247](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L247) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L273) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L292) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L318) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L337) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L363) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L382) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L408) -- [apiShareOperationsToShares2/uploadToShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L47) -- [apiShareOperationsToShares2/uploadToShare.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L78) -- [apiShareOperationsToShares2/uploadToShare.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L111) -- [apiShareOperationsToShares2/uploadToShare.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L140) -- [apiShareOperationsToShares2/uploadToShare.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L171) -- [apiShareOperationsToShares2/uploadToShare.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L346) -- [apiShareOperationsToShares2/uploadToShare.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L347) -- [apiWebdavProperties1/copyFile.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L89) -- [apiWebdavProperties1/copyFile.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L116) -- [apiWebdavProperties1/copyFile.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L292) -- [apiWebdavProperties1/copyFile.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L315) -- [apiWebdavProperties1/copyFile.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L343) -- [apiWebdavProperties1/copyFile.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L373) -- [apiWebdavProperties1/copyFile.feature:402](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L402) -- [apiWebdavProperties1/copyFile.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L431) -- [apiWebdavProperties1/copyFile.feature:515](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L515) -- [apiWebdavProperties1/copyFile.feature:548](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L548) -- [apiWebdavProperties1/copyFile.feature:580](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L580) -- [apiWebdavProperties1/copyFile.feature:612](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L612) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L37) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L38) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L39) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L61) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L62) -- [apiWebdavUploadTUS/uploadToShare.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L31) -- [apiWebdavUploadTUS/uploadToShare.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L50) -- [apiWebdavUploadTUS/uploadToShare.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L72) -- [apiWebdavUploadTUS/uploadToShare.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L93) -- [apiWebdavUploadTUS/uploadToShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L135) -- [apiWebdavUploadTUS/uploadToShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L159) -- [apiWebdavUploadTUS/uploadToShare.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L182) -- [apiWebdavUploadTUS/uploadToShare.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L205) -- [apiWebdavUploadTUS/uploadToShare.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L230) -- [apiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L254) -- [apiWebdavUploadTUS/uploadToShare.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L301) -- [apiWebdavUploadTUS/uploadToShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L326) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L189) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L223) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L264) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L305) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L346) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L387) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L120) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L156) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L194) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L232) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L194) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L238) -- [apiWebdavEtagPropagation2/createFolder.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L85) -- [apiWebdavEtagPropagation2/createFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L116) -- [apiWebdavEtagPropagation2/upload.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L83) -- [apiWebdavEtagPropagation2/upload.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L113) -- [apiWebdavEtagPropagation2/upload.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L144) -- [apiWebdavEtagPropagation2/upload.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L175) -- [apiWebdavLocks2/resharedSharesToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L117) -- [apiWebdavLocks2/resharedSharesToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L118) -- [apiWebdavLocks2/resharedSharesToShares.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L144) -- [apiWebdavLocks2/resharedSharesToShares.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L145) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L46) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L47) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L48) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L49) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L50) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L80) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L81) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L82) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L83) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L84) -- [apiShareOperationsToShares1/changingFilesShare.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L95) -- [apiShareOperationsToShares1/changingFilesShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L169) +- [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) +- [apiFavorites/favoritesSharingToShares.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L23) +- [apiFavorites/favoritesSharingToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L42) +- [apiFavorites/favoritesSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L43) +- [apiFavorites/favoritesSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L61) +- [apiFavorites/favoritesSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L62) +- [apiFavorites/favoritesSharingToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L102) +- [apiFavorites/favoritesSharingToShares.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L103) +- [apiMain/checksums.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L228) #### [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) - [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) #### [wildcard Access-Control-Allow-Origin](https://github.com/owncloud/ocis/issues/1340) - [apiAuth/cors.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L24) @@ -1155,9 +1045,7 @@ And other missing implementation of favorites #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) - [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) -- [apiAuthWebDav/webDavDELETEAuth.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L150) - [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) -- [apiAuthWebDav/webDavDELETEAuth.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L176) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) @@ -1206,10 +1094,10 @@ And other missing implementation of favorites - [apiCapabilities/capabilities.feature:770](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L770) - [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) - [apiCapabilities/capabilities.feature:821](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L821) -- [apiCapabilities/capabilities.feature:882](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L882) - [apiCapabilities/capabilities.feature:850](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L850) +- [apiCapabilities/capabilities.feature:882](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L882) - [apiCapabilities/capabilities.feature:914](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L914) -- [apiCapabilities/capabilities.feature:948](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L948) +- [apiCapabilities/capabilities.feature:948](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L948) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) @@ -1263,24 +1151,18 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [remote.php/dav/uploads endpoint does not exist](https://github.com/owncloud/ocis/issues/1321) - [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:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L82) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L87) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L132) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) -- [apiWebdavProperties1/copyFile.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L138) - [apiWebdavProperties1/createFolder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L95) - [apiWebdavProperties1/createFolder.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L96) -- [apiWebdavProperties1/createFolder.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L101) - [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) @@ -1290,102 +1172,82 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [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) #### [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) #### [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) + +#### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) +- [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) + +#### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) - [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) +- [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) #### [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:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L72) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L77) - -#### moving a share from the /Shares jail to a user home is no longer supported. -- [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) - -### To triage -_The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) - [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) - [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:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L91) -- [apiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L92) - [apiWebdavUploadTUS/checksums.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L172) - [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L178) - [apiWebdavUploadTUS/checksums.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L224) - [apiWebdavUploadTUS/checksums.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L225) - [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:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L232) -- [apiWebdavUploadTUS/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L233) - [apiWebdavUploadTUS/checksums.feature:280](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L280) - [apiWebdavUploadTUS/checksums.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L281) - [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:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L288) -- [apiWebdavUploadTUS/checksums.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L289) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) -- [apiWebdavUploadTUS/optionsRequest.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L21) - [apiWebdavUploadTUS/optionsRequest.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L33) -- [apiWebdavUploadTUS/optionsRequest.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L47) +- [apiWebdavUploadTUS/optionsRequest.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L59) +- [apiWebdavUploadTUS/optionsRequest.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L85) - [apiWebdavUploadTUS/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L224) - [apiWebdavUploadTUS/uploadToShare.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L225) - [apiWebdavUploadTUS/uploadToShare.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L248) - [apiWebdavUploadTUS/uploadToShare.feature:249](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L249) - [apiWebdavUploadTUS/uploadToShare.feature:272](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L272) - [apiWebdavUploadTUS/uploadToShare.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L273) -- [apiWebdavUploadTUS/uploadToShare.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L278) - [apiWebdavUploadTUS/uploadToShare.feature:320](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L320) - [apiWebdavUploadTUS/uploadToShare.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L321) - [apiWebdavUploadTUS/uploadToShare.feature:372](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L372) - [apiWebdavUploadTUS/uploadToShare.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L373) -- [apiWebdavUploadTUS/uploadToShare.feature:378](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L378) - -#### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) -- [apiWebdavUploadTUS/optionsRequest.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L59) -- [apiWebdavUploadTUS/optionsRequest.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L73) -- [apiWebdavUploadTUS/optionsRequest.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L85) -- [apiWebdavUploadTUS/optionsRequest.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L100) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) -- [apiShareReshareToShares1/reShare.feature:259](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L259) -- [apiShareReshareToShares1/reShare.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L260) +- [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) +- [apiShareReshareToShares1/reShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L270) +- [apiShareReshareToShares1/reShare.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L287) +- [apiShareReshareToShares1/reShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L288) +- [apiShareReshareToShares1/reShare.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L305) +- [apiShareReshareToShares1/reShare.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L306) #### [incorrect ocs(v2) status value when getting info of share that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) _ocs: api compatibility, return correct status code_ @@ -1423,12 +1285,10 @@ _ocs: api compatibility, return correct status code_ #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) - [apiWebdavProperties2/getFileProperties.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L215) - [apiWebdavProperties2/getFileProperties.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L216) -- [apiWebdavProperties2/getFileProperties.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L221) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) - [apiWebdavProperties2/getFileProperties.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L275) - [apiWebdavProperties2/getFileProperties.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L276) -- [apiWebdavProperties2/getFileProperties.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L281) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) - [apiShareUpdateToShares/updateShare.feature:242](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L242) @@ -1443,10 +1303,8 @@ _ocs: api compatibility, return correct status code_ #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) - [apiWebdavProperties1/copyFile.feature:452](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L452) - [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) -- [apiWebdavProperties1/copyFile.feature:458](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L458) - [apiWebdavProperties1/copyFile.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L478) - [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) -- [apiWebdavProperties1/copyFile.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L484) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) - [apiVersions/fileVersions.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L426) @@ -1487,97 +1345,29 @@ _ocs: api compatibility, return correct status code_ ### [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) #### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L96) - -### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) -- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) -- [apiAuthWebDav/webDavPUTAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L70) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L52) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L55) #### [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) -#### [TUS upload file with invalid name sends false response](https://github.com/owncloud/ocis/issues/3050) -- [apiWebdavUploadTUS/uploadFile.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L215) -- [apiWebdavUploadTUS/uploadFile.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L216) -- [apiWebdavUploadTUS/uploadFile.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L218) - -#### [unable to create resource using TUS inside Shares dir](https://github.com/owncloud/ocis/issues/3048) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L33) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L52) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L73) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L94) -- [apiWebdavUploadTUS/uploadToShare.feature:352](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L352) - #### [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: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:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L41) - [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) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/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:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L147) -- [apiFavorites/favorites.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L273) #### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) - [apiWebdavOperations/search.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L273) - [apiWebdavOperations/search.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L289) - [apiWebdavOperations/search.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L314) -#### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) -- [apiWebdavOperations/listFiles.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L180) - -### [[spaces webdav] upload to a share that was locked by owner ends with status code 409](https://github.com/owncloud/ocis/issues/3128) -- [apiWebdavLocks2/resharedSharesToShares.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L39) -- [apiWebdavLocks2/resharedSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L40) -- [apiWebdavLocks2/resharedSharesToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L69) -- [apiWebdavLocks2/resharedSharesToShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L70) - -#### [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) - -#### [can't access public link resources with spaces webdav API](https://github.com/owncloud/ocis/issues/3085) - -- [apiWebdavOperations/listFiles.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L216) -- [apiWebdavOperations/listFiles.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L254) -- [apiWebdavOperations/listFiles.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L291) - -#### [Trying to modify a shared file using spaces end-point returns 409 HTTP status code](https://github.com/owncloud/ocis/issues/3241) - -- [apiMain/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L233) - -#### empty permissions on a link is now allowed - -- [apiShareUpdateToShares/updateShare.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L113) -- [apiShareUpdateToShares/updateShare.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L114) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L116) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L117) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L131) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L132) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L26) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L29) - -#### resource path is no longer included in the returned error message -- [apiWebdavProperties2/getFileProperties.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L324) -- [apiWebdavProperties2/getFileProperties.feature:336](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L336) -- [apiWebdavProperties2/getFileProperties.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L337) - Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 0226b2530f..5d6c8da406 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -4,12 +4,12 @@ Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking. #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) -- [apiTrashbin/trashbinFilesFolders.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L208) -- [apiTrashbin/trashbinFilesFolders.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L209) -- [apiTrashbin/trashbinFilesFolders.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L223) -- [apiTrashbin/trashbinFilesFolders.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L224) -- [apiTrashbin/trashbinFilesFolders.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L238) -- [apiTrashbin/trashbinFilesFolders.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L239) +- [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) +- [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) +- [apiTrashbin/trashbinFilesFolders.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L215) +- [apiTrashbin/trashbinFilesFolders.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L216) +- [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) +- [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) - [apiVersions/fileVersions.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L426) @@ -32,124 +32,105 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) - [apiWebdavProperties1/copyFile.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L309) - [apiWebdavProperties1/copyFile.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L310) +- [apiWebdavProperties1/copyFile.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L337) +- [apiWebdavProperties1/copyFile.feature:338](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L338) +- [apiWebdavProperties1/copyFile.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L367) +- [apiWebdavProperties1/copyFile.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L368) +- [apiWebdavProperties1/copyFile.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L396) +- [apiWebdavProperties1/copyFile.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L397) +- [apiWebdavProperties1/copyFile.feature:425](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L425) +- [apiWebdavProperties1/copyFile.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L426) +- [apiWebdavProperties1/copyFile.feature:509](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L509) +- [apiWebdavProperties1/copyFile.feature:542](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L542) +- [apiWebdavProperties1/copyFile.feature:543](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L543) +- [apiWebdavProperties1/copyFile.feature:574](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L574) +- [apiWebdavProperties1/copyFile.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L575) +- [apiWebdavProperties1/copyFile.feature:606](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L606) #### [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: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) #### [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:401](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L401) - [apiWebdavProperties2/getFileProperties.feature:406](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L406) -- [apiWebdavProperties2/getFileProperties.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L411) ### Sync 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:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L381) -- [apiMain/checksums.feature:386](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L386) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) +- [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) +- [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) +- [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) +- [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) - [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) - [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) - [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) - [apiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L46) -- [apiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L51) -- [apiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L52) - [apiWebdavLocks/exclusiveLocks.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L69) - [apiWebdavLocks/exclusiveLocks.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L70) - [apiWebdavLocks/exclusiveLocks.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L71) - [apiWebdavLocks/exclusiveLocks.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L72) -- [apiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L77) -- [apiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L78) - [apiWebdavLocks/exclusiveLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L94) - [apiWebdavLocks/exclusiveLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L95) - [apiWebdavLocks/exclusiveLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L96) - [apiWebdavLocks/exclusiveLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L97) -- [apiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L102) -- [apiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L103) - [apiWebdavLocks/exclusiveLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L120) - [apiWebdavLocks/exclusiveLocks.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L121) - [apiWebdavLocks/exclusiveLocks.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L122) - [apiWebdavLocks/exclusiveLocks.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L123) -- [apiWebdavLocks/exclusiveLocks.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L128) -- [apiWebdavLocks/exclusiveLocks.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L129) - [apiWebdavLocks/exclusiveLocks.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L147) - [apiWebdavLocks/exclusiveLocks.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L148) - [apiWebdavLocks/exclusiveLocks.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L149) - [apiWebdavLocks/exclusiveLocks.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L150) -- [apiWebdavLocks/exclusiveLocks.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L155) -- [apiWebdavLocks/exclusiveLocks.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L156) - [apiWebdavLocks/exclusiveLocks.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L174) - [apiWebdavLocks/exclusiveLocks.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L175) - [apiWebdavLocks/exclusiveLocks.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L176) - [apiWebdavLocks/exclusiveLocks.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L177) -- [apiWebdavLocks/exclusiveLocks.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L182) -- [apiWebdavLocks/exclusiveLocks.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L183) - [apiWebdavLocks/exclusiveLocks.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L201) - [apiWebdavLocks/exclusiveLocks.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L202) - [apiWebdavLocks/exclusiveLocks.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L203) - [apiWebdavLocks/exclusiveLocks.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L204) -- [apiWebdavLocks/exclusiveLocks.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L209) -- [apiWebdavLocks/exclusiveLocks.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L210) - [apiWebdavLocks/folder.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L18) - [apiWebdavLocks/folder.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L19) - [apiWebdavLocks/folder.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L20) - [apiWebdavLocks/folder.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L21) -- [apiWebdavLocks/folder.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L26) -- [apiWebdavLocks/folder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L27) - [apiWebdavLocks/folder.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L41) - [apiWebdavLocks/folder.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L42) - [apiWebdavLocks/folder.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L43) - [apiWebdavLocks/folder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L44) -- [apiWebdavLocks/folder.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L49) -- [apiWebdavLocks/folder.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L50) - [apiWebdavLocks/folder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L63) - [apiWebdavLocks/folder.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L64) - [apiWebdavLocks/folder.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L65) - [apiWebdavLocks/folder.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L66) -- [apiWebdavLocks/folder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L71) -- [apiWebdavLocks/folder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L72) - [apiWebdavLocks/folder.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L86) - [apiWebdavLocks/folder.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L87) - [apiWebdavLocks/folder.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L88) - [apiWebdavLocks/folder.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L89) -- [apiWebdavLocks/folder.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L94) -- [apiWebdavLocks/folder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L95) - [apiWebdavLocks/folder.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L110) - [apiWebdavLocks/folder.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L111) - [apiWebdavLocks/folder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L112) - [apiWebdavLocks/folder.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L113) -- [apiWebdavLocks/folder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L118) -- [apiWebdavLocks/folder.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L119) - [apiWebdavLocks/folder.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L135) - [apiWebdavLocks/folder.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L136) - [apiWebdavLocks/folder.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L137) - [apiWebdavLocks/folder.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L138) -- [apiWebdavLocks/folder.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L144) -- [apiWebdavLocks/folder.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L143) - [apiWebdavLocks/folder.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L161) - [apiWebdavLocks/folder.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L162) - [apiWebdavLocks/folder.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L163) - [apiWebdavLocks/folder.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L164) -- [apiWebdavLocks/folder.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L169) -- [apiWebdavLocks/folder.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L170) - [apiWebdavLocks/publicLink.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L33) - [apiWebdavLocks/publicLink.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L34) - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) - [apiWebdavLocks/publicLink.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L36) -- [apiWebdavLocks/publicLink.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L41) -- [apiWebdavLocks/publicLink.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L42) -- [apiWebdavLocks/publicLink.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L43) -- [apiWebdavLocks/publicLink.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L44) - [apiWebdavLocks/publicLink.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L60) - [apiWebdavLocks/publicLink.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L61) - [apiWebdavLocks/publicLink.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) @@ -176,19 +157,14 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L74) - [apiWebdavLocks/requestsWithToken.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L75) - [apiWebdavLocks/requestsWithToken.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L76) -- [apiWebdavLocks/requestsWithToken.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L81) -- [apiWebdavLocks/requestsWithToken.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L82) - [apiWebdavLocks/requestsWithToken.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L100) - [apiWebdavLocks/requestsWithToken.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L101) - [apiWebdavLocks/requestsWithToken.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L102) - [apiWebdavLocks/requestsWithToken.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L103) - [apiWebdavLocks/requestsWithToken.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L131) - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) -- [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) -- [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) - [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L156) - [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L157) -- [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L31) - [apiWebdavLocks2/resharedSharesToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L32) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) @@ -213,8 +189,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L25) - [apiWebdavLocks2/setTimeout.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L26) - [apiWebdavLocks2/setTimeout.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L27) -- [apiWebdavLocks2/setTimeout.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L32) -- [apiWebdavLocks2/setTimeout.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L33) - [apiWebdavLocks2/setTimeout.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L47) - [apiWebdavLocks2/setTimeout.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L48) - [apiWebdavLocks2/setTimeout.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L49) @@ -225,11 +199,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L54) - [apiWebdavLocks2/setTimeout.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L55) - [apiWebdavLocks2/setTimeout.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L56) -- [apiWebdavLocks2/setTimeout.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L61) -- [apiWebdavLocks2/setTimeout.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L62) -- [apiWebdavLocks2/setTimeout.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L63) -- [apiWebdavLocks2/setTimeout.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L64) -- [apiWebdavLocks2/setTimeout.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L65) - [apiWebdavLocks2/setTimeout.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L81) - [apiWebdavLocks2/setTimeout.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L82) - [apiWebdavLocks2/setTimeout.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L83) @@ -242,12 +211,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeout.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L90) - [apiWebdavLocks2/setTimeout.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L91) - [apiWebdavLocks2/setTimeout.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L92) -- [apiWebdavLocks2/setTimeout.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L97) -- [apiWebdavLocks2/setTimeout.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L98) -- [apiWebdavLocks2/setTimeout.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L99) -- [apiWebdavLocks2/setTimeout.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L100) -- [apiWebdavLocks2/setTimeout.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L101) -- [apiWebdavLocks2/setTimeout.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L102) - [apiWebdavLocks2/setTimeout.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L117) - [apiWebdavLocks2/setTimeout.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L118) - [apiWebdavLocks2/setTimeout.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L119) @@ -282,20 +245,14 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks3/independentLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L26) - [apiWebdavLocks3/independentLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L27) - [apiWebdavLocks3/independentLocks.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L28) -- [apiWebdavLocks3/independentLocks.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L33) -- [apiWebdavLocks3/independentLocks.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L34) - [apiWebdavLocks3/independentLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L51) - [apiWebdavLocks3/independentLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L52) - [apiWebdavLocks3/independentLocks.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L53) - [apiWebdavLocks3/independentLocks.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L54) -- [apiWebdavLocks3/independentLocks.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L59) -- [apiWebdavLocks3/independentLocks.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L60) - [apiWebdavLocks3/independentLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L77) - [apiWebdavLocks3/independentLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L78) - [apiWebdavLocks3/independentLocks.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L79) - [apiWebdavLocks3/independentLocks.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L80) -- [apiWebdavLocks3/independentLocks.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L85) -- [apiWebdavLocks3/independentLocks.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L86) - [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) @@ -304,101 +261,70 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks3/independentLocks.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L110) - [apiWebdavLocks3/independentLocks.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L111) - [apiWebdavLocks3/independentLocks.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L112) -- [apiWebdavLocks3/independentLocks.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L117) -- [apiWebdavLocks3/independentLocks.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L118) -- [apiWebdavLocks3/independentLocks.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L119) -- [apiWebdavLocks3/independentLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L120) - [apiWebdavLocks3/independentLocksShareToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L28) - [apiWebdavLocks3/independentLocksShareToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L29) - [apiWebdavLocks3/independentLocksShareToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L30) - [apiWebdavLocks3/independentLocksShareToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L31) -- [apiWebdavLocks3/independentLocksShareToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L36) -- [apiWebdavLocks3/independentLocksShareToShares.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L37) - [apiWebdavLocks3/independentLocksShareToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L57) - [apiWebdavLocks3/independentLocksShareToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L58) - [apiWebdavLocks3/independentLocksShareToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L59) - [apiWebdavLocks3/independentLocksShareToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L60) -- [apiWebdavLocks3/independentLocksShareToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L65) -- [apiWebdavLocks3/independentLocksShareToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L66) - [apiWebdavLocks3/independentLocksShareToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L87) - [apiWebdavLocks3/independentLocksShareToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L88) - [apiWebdavLocks3/independentLocksShareToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L89) - [apiWebdavLocks3/independentLocksShareToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L90) -- [apiWebdavLocks3/independentLocksShareToShares.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L95) -- [apiWebdavLocks3/independentLocksShareToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L96) - [apiWebdavLocks3/independentLocksShareToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L116) - [apiWebdavLocks3/independentLocksShareToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L117) - [apiWebdavLocks3/independentLocksShareToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L118) - [apiWebdavLocks3/independentLocksShareToShares.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L119) -- [apiWebdavLocks3/independentLocksShareToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L124) -- [apiWebdavLocks3/independentLocksShareToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L125) -- [apiWebdavLocksUnlock/unlock.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L30) -- [apiWebdavLocksUnlock/unlock.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L31) +- [apiWebdavLocksUnlock/unlock.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L22) +- [apiWebdavLocksUnlock/unlock.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L23) +- [apiWebdavLocksUnlock/unlock.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L24) +- [apiWebdavLocksUnlock/unlock.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L25) - [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) - [apiWebdavLocksUnlock/unlock.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L47) -- [apiWebdavLocksUnlock/unlock.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L52) - [apiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L68) - [apiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L69) - [apiWebdavLocksUnlock/unlock.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L70) - [apiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L71) -- [apiWebdavLocksUnlock/unlock.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L76) -- [apiWebdavLocksUnlock/unlock.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L77) - [apiWebdavLocksUnlock/unlock.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L91) - [apiWebdavLocksUnlock/unlock.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L92) - [apiWebdavLocksUnlock/unlock.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L116) - [apiWebdavLocksUnlock/unlock.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L117) - [apiWebdavLocksUnlock/unlock.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L118) - [apiWebdavLocksUnlock/unlock.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L119) -- [apiWebdavLocksUnlock/unlock.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L124) -- [apiWebdavLocksUnlock/unlock.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L125) - [apiWebdavLocksUnlock/unlock.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L147) - [apiWebdavLocksUnlock/unlock.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L148) - [apiWebdavLocksUnlock/unlock.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L149) - [apiWebdavLocksUnlock/unlock.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L150) -- [apiWebdavLocksUnlock/unlock.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L155) -- [apiWebdavLocksUnlock/unlock.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L156) - [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:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L44) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L45) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L61) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L62) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L63) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L68) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L69) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L90) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L91) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L92) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L93) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L98) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L115) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L116) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L117) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L118) -- [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: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:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L151) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L164) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L165) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L180) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L181) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L182) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L183) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L188) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L189) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L210) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L211) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L212) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L213) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L218) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L219) ### Share File and sync features in a shared scenario @@ -425,19 +351,33 @@ File and sync features in a shared scenario #### [cannot accept identical pending shares from different user serially](https://github.com/owncloud/ocis/issues/2131) - [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) - [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L162) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L163) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L202) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L203) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L45) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L46) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) + +#### [Shares received in different ways are not merged](https://github.com/owncloud/ocis/issues/2711) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:582](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L582) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:583](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L583) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L608) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:609](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L609) + +#### [file_target in share response](https://github.com/owncloud/product/issues/203) + +- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:290](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L290) +- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L291) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L59) +- [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) #### [Fix accepting/denying group shares](https://github.com/cs3org/reva/issues/1769) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:461](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L461) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L462) - #### [Cannot move a file to a shared folder](https://github.com/owncloud/ocis/issues/2146) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:509](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L509) @@ -489,25 +429,17 @@ File and sync features in a shared scenario - [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) -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) +#### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) -- [apiSharePublicLink1/createPublicLinkShare.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L375) -- [apiSharePublicLink1/createPublicLinkShare.feature:376](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L376) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L212) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L213) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L214) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L215) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L44) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L45) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L74) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L75) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L104) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L105) +- [apiSharePublicLink1/createPublicLinkShare.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L141) +- [apiSharePublicLink1/createPublicLinkShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L142) +- [apiSharePublicLink1/createPublicLinkShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L218) +- [apiSharePublicLink1/createPublicLinkShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L219) -#### [Public cannot upload file with mtime set on a public link share with new version of WebDAV API](https://github.com/owncloud/core/issues/37605) +#### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) -- [apiSharePublicLink1/createPublicLinkShare.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L587) -- [apiSharePublicLink1/createPublicLinkShare.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L608) +- [apiSharePublicLink1/createPublicLinkShare.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L105) +- [apiSharePublicLink1/createPublicLinkShare.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L106) #### [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) @@ -541,14 +473,22 @@ File and sync features in a shared scenario #### [Set quota over settings](https://github.com/owncloud/ocis/issues/1290) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ + - [apiSharePublicLink2/uploadToPublicLinkShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L160) - [apiSharePublicLink2/uploadToPublicLinkShare.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L179) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) +- [apiSharePublicLink1/createPublicLinkShare.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L375) +- [apiSharePublicLink1/createPublicLinkShare.feature:376](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L376) +- [apiSharePublicLink1/createPublicLinkShare.feature:477](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L477) +- [apiSharePublicLink1/createPublicLinkShare.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L478) +- [apiSharePublicLink1/createPublicLinkShare.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L566) +- [apiSharePublicLink1/createPublicLinkShare.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L567) +- [apiSharePublicLink1/createPublicLinkShare.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L587) +- [apiSharePublicLink1/createPublicLinkShare.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L608) - [apiSharePublicLink2/uploadToPublicLinkShare.feature:198](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L198) -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L27) - [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L28) - [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L45) @@ -556,29 +496,55 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares2/reShareDisabled.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareDisabled.feature#L27) - [apiShareReshareToShares2/reShareDisabled.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareDisabled.feature#L28) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L212) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L213) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L214) +- [apiShareManagementBasicToShares/deleteShareFromShares.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L215) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L44) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L45) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L74) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L75) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L104) +- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L105) + +- [apiShareUpdateToShares/updateShare.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L324) +- [apiShareUpdateToShares/updateShare.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L325) +- [apiShareUpdateToShares/updateShare.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L350) +- [apiShareUpdateToShares/updateShare.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L351) +- [apiShareUpdateToShares/updateShare.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L367) +- [apiShareUpdateToShares/updateShare.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L368) +- [apiShareUpdateToShares/updateShare.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L396) +- [apiShareUpdateToShares/updateShare.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L397) +- [apiShareUpdateToShares/updateShare.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L427) +- [apiShareUpdateToShares/updateShare.feature:428](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L428) + #### [share permissions are not enforced](https://github.com/owncloud/product/issues/270) - [apiShareManagementToShares/mergeShare.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L104) - [apiShareReshareToShares3/reShareUpdate.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L61) - [apiShareReshareToShares3/reShareUpdate.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L62) #### [file_target in share response](https://github.com/owncloud/product/issues/203) -- [apiShareReshareToShares2/reShareSubfolder.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L178) -- [apiShareReshareToShares2/reShareSubfolder.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L179) +- [apiShareReshareToShares2/reShareSubfolder.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L180) +- [apiShareReshareToShares2/reShareSubfolder.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L181) #### [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:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L42) -- [apiTrashbin/trashbinSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L43) -- [apiTrashbin/trashbinSharingToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L65) -- [apiTrashbin/trashbinSharingToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L66) -- [apiTrashbin/trashbinSharingToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L88) -- [apiTrashbin/trashbinSharingToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L89) -- [apiTrashbin/trashbinSharingToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L112) -- [apiTrashbin/trashbinSharingToShares.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L113) -- [apiTrashbin/trashbinSharingToShares.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L136) -- [apiTrashbin/trashbinSharingToShares.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L137) -- [apiTrashbin/trashbinSharingToShares.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L160) -- [apiTrashbin/trashbinSharingToShares.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L161) +- [apiTrashbin/trashbinSharingToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L40) +- [apiTrashbin/trashbinSharingToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L41) +- [apiTrashbin/trashbinSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L62) +- [apiTrashbin/trashbinSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L63) +- [apiTrashbin/trashbinSharingToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L84) +- [apiTrashbin/trashbinSharingToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L85) +- [apiTrashbin/trashbinSharingToShares.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L107) +- [apiTrashbin/trashbinSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L108) +- [apiTrashbin/trashbinSharingToShares.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L130) +- [apiTrashbin/trashbinSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L131) +- [apiTrashbin/trashbinSharingToShares.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L154) +- [apiTrashbin/trashbinSharingToShares.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L155) + +#### [Folder overwrite on shared files doesn't works correctly on copying file](https://github.com/owncloud/ocis/issues/2183) +- [apiWebdavProperties1/copyFile.feature:510](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L510) +- [apiWebdavProperties1/copyFile.feature:607](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L607) #### [changing user quota gives ocs status 103 / cannot set user quota using the ocs endpoint](https://github.com/owncloud/product/issues/247) _getting and setting quota_ @@ -604,83 +570,70 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) - [apiWebdavProperties1/getQuota.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L19) -- [apiWebdavProperties1/getQuota.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L24) - Scenario Outline: Retrieving folder quota when quota is set - [apiWebdavProperties1/getQuota.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L34) - [apiWebdavProperties1/getQuota.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L35) -- [apiWebdavProperties1/getQuota.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L40) Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient - [apiWebdavProperties1/getQuota.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L61) - [apiWebdavProperties1/getQuota.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L62) -Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded -- [apiWebdavProperties1/getQuota.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L67) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded - [apiWebdavProperties1/getQuota.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L81) - [apiWebdavProperties1/getQuota.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L82) -Scenario Outline: Retrieving folder quota when quota is set and a file was received -- [apiWebdavProperties1/getQuota.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L87) Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L103) - [apiWebdavProperties1/getQuota.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L104) -- [apiWebdavProperties1/getQuota.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L109) + +#### [cannot get share-types webdav property](https://github.com/owncloud/ocis/issues/567) +- [apiWebdavProperties2/getFileProperties.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L238) +- [apiWebdavProperties2/getFileProperties.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L239) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) - [apiWebdavProperties2/getFileProperties.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L306) - [apiWebdavProperties2/getFileProperties.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L307) -- [apiWebdavProperties2/getFileProperties.feature:312](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L312) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ - [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) - [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) -- [apiShareOperationsToShares2/uploadToShare.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L199) - [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) - [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) -- [apiShareOperationsToShares2/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L224) - [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) -- [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) - [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) - [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) -- [apiShareOperationsToShares2/uploadToShare.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L276) - [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) - [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) -- [apiShareOperationsToShares2/uploadToShare.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L303) + #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L30) - [apiShareOperationsToShares1/changingFilesShare.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L115) - [apiShareOperationsToShares1/changingFilesShare.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L116) -- [apiShareOperationsToShares1/changingFilesShare.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L121) - [apiShareOperationsToShares1/changingFilesShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L142) - [apiShareOperationsToShares1/changingFilesShare.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L143) -- [apiShareOperationsToShares1/changingFilesShare.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L148) +- [apiShareOperationsToShares1/changingFilesShare.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L163) +- [apiShareOperationsToShares1/changingFilesShare.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L164) - [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:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L38) -- [apiWebdavMove2/moveShareOnOcis.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L39) - [apiWebdavMove2/moveShareOnOcis.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L60) - [apiWebdavMove2/moveShareOnOcis.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L62) -- [apiWebdavMove2/moveShareOnOcis.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L68) - [apiWebdavMove2/moveShareOnOcis.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L91) - [apiWebdavMove2/moveShareOnOcis.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L93) -- [apiWebdavMove2/moveShareOnOcis.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L99) -- [apiWebdavMove2/moveShareOnOcis.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L100) - [apiWebdavMove2/moveShareOnOcis.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L124) - [apiWebdavMove2/moveShareOnOcis.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L126) -- [apiWebdavMove2/moveShareOnOcis.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L132) - [apiWebdavMove2/moveShareOnOcis.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L152) - [apiWebdavMove2/moveShareOnOcis.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L153) -- [apiWebdavMove2/moveShareOnOcis.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L158) -- [apiWebdavMove2/moveShareOnOcis.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L185) -- [apiWebdavMove2/moveShareOnOcis.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L220) +- [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) + +#### [OCIS-storage overwriting a file as share receiver, does not create a new file version for the sharer](https://github.com/owncloud/ocis/issues/766) +- [apiVersions/fileVersionsSharingToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L32) #### [restoring an older version of a shared file deletes the share](https://github.com/owncloud/ocis/issues/765) - [apiShareManagementToShares/acceptShares.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L587) +- [apiVersions/fileVersionsSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L43) #### [Resharing does not work with ocis storage](https://github.com/owncloud/product/issues/265) @@ -712,42 +665,36 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) #### [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) +#### Expiration date of user shares- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L29) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L30) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L58) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L59) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L86) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L87) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L113) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L114) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L140) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L141) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L162) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L163) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L303) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:304](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L304) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L325) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L326) - [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L347) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:661](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L661) -- [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:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L348) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L370) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L371) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:388](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L388) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L389) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:406](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L406) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:407](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L407) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L566) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L567) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:584](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L584) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:585](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L585) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:606](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L606) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:607](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L607) - [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) @@ -758,36 +705,67 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [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: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) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:661](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L661) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:662](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L662) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:663](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L663) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:664](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L664) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:665](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L665) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L666) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:667](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L667) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L688) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:689](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L689) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:690](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L690) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:691](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L691) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:692](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L692) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:693](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L693) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:714](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L714) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:715](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L715) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:716](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L716) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:717](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L717) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L718) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:719](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L719) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:740](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L740) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:741](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L741) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:762](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L762) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:763](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L763) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:784](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L784) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:785](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L785) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L36) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L37) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L92) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L93) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L93) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L94) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L95) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L124) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L125) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L126) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L127) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L153) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L154) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L155) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L156) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:186](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L186) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L187) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L215) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L216) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L217) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L218) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L273) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L274) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L275) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L276) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L305) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L306) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L307) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:308](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L308) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:338](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L338) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:339](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L339) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:340](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L340) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L341) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:368](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L368) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L369) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L370) +- [apiShareReshareToShares3/reShareWithExpiryDate.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L371) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L403) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:404](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L404) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:405](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L405) @@ -802,26 +780,26 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares3/reShareWithExpiryDate.feature:469](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L469) #### 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:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L193) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L194) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L223) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L224) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:252](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L252) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L253) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L282) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L283) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:429](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L429) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L430) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L453) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L454) - [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) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L478) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L502) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L503) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:524](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L524) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:525](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L525) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:546](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L546) +- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:547](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L547) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L64) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L65) - [apiShareReshareToShares3/reShareWithExpiryDate.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L124) @@ -834,13 +812,11 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareReshareToShares3/reShareWithExpiryDate.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L251) #### Expiration date of public link shares -- [apiSharePublicLink1/createPublicLinkShare.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L566) -- [apiSharePublicLink1/createPublicLinkShare.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L567) #### [incorrect ocs(v2) status value when sharing to group that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) _ocs: api compatibility, return correct status code_ -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L229) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L230) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L49) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L50) - [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L51) @@ -858,39 +834,27 @@ _ocs: api compatibility, return correct status code_ - [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) - [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) +- [apiShareUpdateToShares/updateShare.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L130) - [apiShareUpdateToShares/updateShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L131) - [apiShareUpdateToShares/updateShare.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L132) - [apiShareUpdateToShares/updateShare.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L133) - [apiShareUpdateToShares/updateShare.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L134) - [apiShareUpdateToShares/updateShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L135) -- [apiShareUpdateToShares/updateShare.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L136) +- [apiShareUpdateToShares/updateShare.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L154) - [apiShareUpdateToShares/updateShare.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L155) - [apiShareUpdateToShares/updateShare.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L156) - [apiShareUpdateToShares/updateShare.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L157) - [apiShareUpdateToShares/updateShare.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L158) - [apiShareUpdateToShares/updateShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L159) -- [apiShareUpdateToShares/updateShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L160) #### [Group shares support ](https://github.com/owncloud/ocis/issues/1289) - [apiShareOperationsToShares1/gettingShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L188) -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) -- [apiShareUpdateToShares/updateShare.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L325) -- [apiShareUpdateToShares/updateShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L326) -- [apiShareUpdateToShares/updateShare.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L350) -- [apiShareUpdateToShares/updateShare.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L351) -- [apiShareUpdateToShares/updateShare.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L369) -- [apiShareUpdateToShares/updateShare.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L370) -- [apiShareUpdateToShares/updateShare.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L396) -- [apiShareUpdateToShares/updateShare.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L397) -- [apiShareUpdateToShares/updateShare.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L426) -- [apiShareUpdateToShares/updateShare.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L427) - #### [Share additional info](https://github.com/owncloud/ocis/issues/1253) #### [Share extra attributes](https://github.com/owncloud/ocis/issues/1224) #### [Edit user share response has an "name" field](https://github.com/owncloud/ocis/issues/1225) +- [apiShareUpdateToShares/updateShare.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L287) - [apiShareUpdateToShares/updateShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L288) -- [apiShareUpdateToShares/updateShare.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L289) #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) - [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) @@ -899,6 +863,9 @@ _ocs: api compatibility, return correct status code_ - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:670](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L670) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:671](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L671) +#### [OCIS-storage overwriting a file as share receiver, does not create a new file version for the sharer](https://github.com/owncloud/ocis/issues/766) +- [apiVersions/fileVersionsSharingToShares.feature:294](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L294) + #### [deleting a share with wrong authentication returns OCS status 996 / HTTP 500](https://github.com/owncloud/ocis/issues/1229) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:250](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L250) - [apiShareManagementBasicToShares/deleteShareFromShares.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L251) @@ -915,6 +882,7 @@ 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:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously +- [apiAuthOcs/ocsGETAuth.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L31) Scenario: ocs config end point accessible by unauthorized users - [apiAuthOcs/ocsGETAuth.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L51) Scenario: using OCS with non-admin basic auth - [apiAuthOcs/ocsGETAuth.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L84) Scenario: using OCS as normal user with wrong password - [apiAuthOcs/ocsGETAuth.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L115) Scenario:using OCS with admin basic auth @@ -924,31 +892,26 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ +- [apiAuthWebDav/webDavDELETEAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L58) Scenario: send DELETE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPROPFINDAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPROPFINDAuth.feature#L57) Scenario: send PROPFIND requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPROPPATCHAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPROPPATCHAuth.feature#L58) Scenario: send PROPPATCH requests to another user's webDav endpoints as normal user - [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMKCOLAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L68) Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) - [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavLOCKAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L70) Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMOVEAuth.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L66) Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [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:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavPOSTAuth.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L67) Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [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:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) - [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) -- [apiAuthWebDav/webDavSpecialURLs.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L45) - [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: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) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -957,149 +920,51 @@ _ocdav: api compatibility, return correct status code_ #### [REPORT request not implemented](https://github.com/owncloud/ocis/issues/1330) - [apiWebdavOperations/search.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L42) - [apiWebdavOperations/search.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L43) -- [apiWebdavOperations/search.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L48) - [apiWebdavOperations/search.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L64) - [apiWebdavOperations/search.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L65) -- [apiWebdavOperations/search.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L70) - [apiWebdavOperations/search.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L87) - [apiWebdavOperations/search.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L88) -- [apiWebdavOperations/search.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L93) - [apiWebdavOperations/search.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L102) - [apiWebdavOperations/search.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L103) -- [apiWebdavOperations/search.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L108) - [apiWebdavOperations/search.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L126) - [apiWebdavOperations/search.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L127) -- [apiWebdavOperations/search.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L132) - [apiWebdavOperations/search.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L150) - [apiWebdavOperations/search.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L151) -- [apiWebdavOperations/search.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L156) - [apiWebdavOperations/search.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L174) - [apiWebdavOperations/search.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L175) -- [apiWebdavOperations/search.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L180) - [apiWebdavOperations/search.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L207) - [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) -- [apiWebdavOperations/search.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L213) - [apiWebdavOperations/search.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L239) - [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) -- [apiWebdavOperations/search.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L245) - [apiWebdavOperations/search.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L264) - [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) -- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) And other missing implementation of favorites - [apiFavorites/favorites.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L162) - [apiFavorites/favorites.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L163) - [apiFavorites/favorites.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L187) - [apiFavorites/favorites.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L188) -- [apiFavorites/favorites.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L193) - [apiFavorites/favorites.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L220) - [apiFavorites/favorites.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L221) -- [apiFavorites/favorites.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L226) - [apiFavorites/favoritesSharingToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L82) -- [apiFavorites/favoritesSharingToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L88) +- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) #### [resource inside Shares dir is not found using the spaces WebDAV API](https://github.com/owncloud/ocis/issues/2968) -- [apiFavorites/favorites.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L168) -- [apiFavorites/favoritesSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L28) -- [apiFavorites/favoritesSharingToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L48) -- [apiFavorites/favoritesSharingToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L67) -- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) -- [apiFavorites/favoritesSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L108) -- [apiMain/checksums.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L211) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L49) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L75) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L94) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L120) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L139) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L165) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L203) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L228) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:247](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L247) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L273) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L292) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L318) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L337) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L363) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L382) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L408) -- [apiShareOperationsToShares2/uploadToShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L47) -- [apiShareOperationsToShares2/uploadToShare.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L78) -- [apiShareOperationsToShares2/uploadToShare.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L111) -- [apiShareOperationsToShares2/uploadToShare.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L140) -- [apiShareOperationsToShares2/uploadToShare.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L171) -- [apiShareOperationsToShares2/uploadToShare.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L346) -- [apiShareOperationsToShares2/uploadToShare.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L347) -- [apiWebdavProperties1/copyFile.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L89) -- [apiWebdavProperties1/copyFile.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L116) -- [apiWebdavProperties1/copyFile.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L292) -- [apiWebdavProperties1/copyFile.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L315) -- [apiWebdavProperties1/copyFile.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L343) -- [apiWebdavProperties1/copyFile.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L373) -- [apiWebdavProperties1/copyFile.feature:402](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L402) -- [apiWebdavProperties1/copyFile.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L431) -- [apiWebdavProperties1/copyFile.feature:515](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L515) -- [apiWebdavProperties1/copyFile.feature:548](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L548) -- [apiWebdavProperties1/copyFile.feature:580](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L580) -- [apiWebdavProperties1/copyFile.feature:612](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L612) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L37) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L38) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L39) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L61) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L62) -- [apiWebdavUploadTUS/uploadToShare.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L31) -- [apiWebdavUploadTUS/uploadToShare.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L50) -- [apiWebdavUploadTUS/uploadToShare.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L72) -- [apiWebdavUploadTUS/uploadToShare.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L93) -- [apiWebdavUploadTUS/uploadToShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L135) -- [apiWebdavUploadTUS/uploadToShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L159) -- [apiWebdavUploadTUS/uploadToShare.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L182) -- [apiWebdavUploadTUS/uploadToShare.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L205) -- [apiWebdavUploadTUS/uploadToShare.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L230) -- [apiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L254) -- [apiWebdavUploadTUS/uploadToShare.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L301) -- [apiWebdavUploadTUS/uploadToShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L326) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L189) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L223) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L264) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L305) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L346) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L387) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L120) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L156) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L194) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L232) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L194) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L238) -- [apiWebdavEtagPropagation2/createFolder.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L85) -- [apiWebdavEtagPropagation2/createFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L116) -- [apiWebdavEtagPropagation2/upload.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L83) -- [apiWebdavEtagPropagation2/upload.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L113) -- [apiWebdavEtagPropagation2/upload.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L144) -- [apiWebdavEtagPropagation2/upload.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L175) -- [apiWebdavLocks2/resharedSharesToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L117) -- [apiWebdavLocks2/resharedSharesToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L118) -- [apiWebdavLocks2/resharedSharesToShares.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L144) -- [apiWebdavLocks2/resharedSharesToShares.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L145) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L46) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L47) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L48) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L49) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L50) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L80) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L81) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L82) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L83) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L84) -- [apiShareOperationsToShares1/changingFilesShare.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L95) -- [apiShareOperationsToShares1/changingFilesShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L169) +- [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) +- [apiFavorites/favoritesSharingToShares.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L23) +- [apiFavorites/favoritesSharingToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L42) +- [apiFavorites/favoritesSharingToShares.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L43) +- [apiFavorites/favoritesSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L61) +- [apiFavorites/favoritesSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L62) +- [apiFavorites/favoritesSharingToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L102) +- [apiFavorites/favoritesSharingToShares.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L103) +- [apiMain/checksums.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L228) #### [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) - [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) #### [wildcard Access-Control-Allow-Origin](https://github.com/owncloud/ocis/issues/1340) - [apiAuth/cors.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L24) @@ -1169,9 +1034,7 @@ And other missing implementation of favorites #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) - [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) -- [apiAuthWebDav/webDavDELETEAuth.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L150) - [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) -- [apiAuthWebDav/webDavDELETEAuth.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L176) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) @@ -1220,10 +1083,10 @@ And other missing implementation of favorites - [apiCapabilities/capabilities.feature:770](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L770) - [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) - [apiCapabilities/capabilities.feature:821](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L821) -- [apiCapabilities/capabilities.feature:882](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L882) - [apiCapabilities/capabilities.feature:850](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L850) +- [apiCapabilities/capabilities.feature:882](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L882) - [apiCapabilities/capabilities.feature:914](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L914) -- [apiCapabilities/capabilities.feature:948](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L948) +- [apiCapabilities/capabilities.feature:948](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L948) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) @@ -1277,24 +1140,18 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [remote.php/dav/uploads endpoint does not exist](https://github.com/owncloud/ocis/issues/1321) - [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:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L82) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L87) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L132) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) -- [apiWebdavProperties1/copyFile.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L138) - [apiWebdavProperties1/createFolder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L95) - [apiWebdavProperties1/createFolder.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L96) -- [apiWebdavProperties1/createFolder.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L101) - [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) @@ -1304,109 +1161,83 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [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) #### [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) #### [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) #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - [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) + #### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) - [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:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L26) - [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:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L43) - [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) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L84) +- [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) #### [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:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L72) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L77) - -#### moving a share from the /Shares jail to a user home is no longer supported. -- [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) - -### To triage -_The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) - [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) - [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:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L91) -- [apiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L92) - [apiWebdavUploadTUS/checksums.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L172) - [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L178) - [apiWebdavUploadTUS/checksums.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L224) - [apiWebdavUploadTUS/checksums.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L225) - [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:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L232) -- [apiWebdavUploadTUS/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L233) - [apiWebdavUploadTUS/checksums.feature:280](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L280) - [apiWebdavUploadTUS/checksums.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L281) - [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:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L288) -- [apiWebdavUploadTUS/checksums.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L289) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) -- [apiWebdavUploadTUS/optionsRequest.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L21) - [apiWebdavUploadTUS/optionsRequest.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L33) -- [apiWebdavUploadTUS/optionsRequest.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L47) +- [apiWebdavUploadTUS/optionsRequest.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L59) +- [apiWebdavUploadTUS/optionsRequest.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L85) - [apiWebdavUploadTUS/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L224) - [apiWebdavUploadTUS/uploadToShare.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L225) - [apiWebdavUploadTUS/uploadToShare.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L248) - [apiWebdavUploadTUS/uploadToShare.feature:249](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L249) - [apiWebdavUploadTUS/uploadToShare.feature:272](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L272) - [apiWebdavUploadTUS/uploadToShare.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L273) -- [apiWebdavUploadTUS/uploadToShare.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L278) - [apiWebdavUploadTUS/uploadToShare.feature:320](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L320) - [apiWebdavUploadTUS/uploadToShare.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L321) - [apiWebdavUploadTUS/uploadToShare.feature:372](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L372) - [apiWebdavUploadTUS/uploadToShare.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L373) -- [apiWebdavUploadTUS/uploadToShare.feature:378](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L378) - -#### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) -- [apiWebdavUploadTUS/optionsRequest.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L59) -- [apiWebdavUploadTUS/optionsRequest.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L73) -- [apiWebdavUploadTUS/optionsRequest.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L85) -- [apiWebdavUploadTUS/optionsRequest.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L100) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) -- [apiShareReshareToShares1/reShare.feature:259](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L259) -- [apiShareReshareToShares1/reShare.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L260) +- [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) +- [apiShareReshareToShares1/reShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L270) +- [apiShareReshareToShares1/reShare.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L287) +- [apiShareReshareToShares1/reShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L288) +- [apiShareReshareToShares1/reShare.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L305) +- [apiShareReshareToShares1/reShare.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L306) #### [incorrect ocs(v2) status value when getting info of share that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) _ocs: api compatibility, return correct status code_ @@ -1444,12 +1275,10 @@ _ocs: api compatibility, return correct status code_ #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) - [apiWebdavProperties2/getFileProperties.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L215) - [apiWebdavProperties2/getFileProperties.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L216) -- [apiWebdavProperties2/getFileProperties.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L221) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) - [apiWebdavProperties2/getFileProperties.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L275) - [apiWebdavProperties2/getFileProperties.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L276) -- [apiWebdavProperties2/getFileProperties.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L281) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) - [apiShareUpdateToShares/updateShare.feature:242](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L242) @@ -1464,10 +1293,10 @@ _ocs: api compatibility, return correct status code_ #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) - [apiWebdavProperties1/copyFile.feature:452](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L452) - [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) -- [apiWebdavProperties1/copyFile.feature:458](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L458) - [apiWebdavProperties1/copyFile.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L478) - [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) -- [apiWebdavProperties1/copyFile.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L484) +- [apiWebdavProperties1/copyFile.feature:543](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L543) +- [apiWebdavProperties1/copyFile.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L575) ### [Allow public link sharing only for certain groups feature not implemented] - [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) @@ -1492,92 +1321,28 @@ _ocs: api compatibility, return correct status code_ ### [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) #### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L96) - -### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) -- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) -- [apiAuthWebDav/webDavPUTAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L70) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L52) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L55) #### [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) -#### [TUS upload file with invalid name sends false response](https://github.com/owncloud/ocis/issues/3050) -- [apiWebdavUploadTUS/uploadFile.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L215) -- [apiWebdavUploadTUS/uploadFile.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L216) -- [apiWebdavUploadTUS/uploadFile.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L218) - -#### [unable to create resource using TUS inside Shares dir](https://github.com/owncloud/ocis/issues/3048) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L33) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L52) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L73) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L94) -- [apiWebdavUploadTUS/uploadToShare.feature:352](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L352) - #### [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: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:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L41) - [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) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/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:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L147) -- [apiFavorites/favorites.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L273) #### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) - [apiWebdavOperations/search.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L273) - [apiWebdavOperations/search.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L289) - [apiWebdavOperations/search.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L314) -#### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) -- [apiWebdavOperations/listFiles.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L180) - -### [[spaces webdav] upload to a share that was locked by owner ends with status code 409](https://github.com/owncloud/ocis/issues/3128) -- [apiWebdavLocks2/resharedSharesToShares.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L39) -- [apiWebdavLocks2/resharedSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L40) -- [apiWebdavLocks2/resharedSharesToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L69) -- [apiWebdavLocks2/resharedSharesToShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L70) - -#### [can't access public link resources with spaces webdav API](https://github.com/owncloud/ocis/issues/3085) - -- [apiWebdavOperations/listFiles.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L216) -- [apiWebdavOperations/listFiles.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L254) -- [apiWebdavOperations/listFiles.feature:291](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L291) - -#### [Trying to modify a shared file using spaces end-point returns 409 HTTP status code](https://github.com/owncloud/ocis/issues/3241) - -- [apiMain/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L233) - -#### empty permissions on a link is now allowed - -- [apiShareUpdateToShares/updateShare.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L113) -- [apiShareUpdateToShares/updateShare.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L114) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L116) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L117) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L131) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L132) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L26) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L29) - -#### resource path is no longer included in the returned error message -- [apiWebdavProperties2/getFileProperties.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L324) -- [apiWebdavProperties2/getFileProperties.feature:336](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L336) -- [apiWebdavProperties2/getFileProperties.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L337) - - Note: always have an empty line at the end of this file. +- Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/oc-integration-tests/drone/permissions-ocis-ci.toml b/tests/oc-integration-tests/drone/permissions-ocis-ci.toml index 489460b82f..ef025245f3 100644 --- a/tests/oc-integration-tests/drone/permissions-ocis-ci.toml +++ b/tests/oc-integration-tests/drone/permissions-ocis-ci.toml @@ -9,4 +9,4 @@ address = "0.0.0.0:10000" [grpc.services.permissions] driver = "demo" -[grpc.services.permissions.drivers.demo] +[grpc.services.publicshareprovider.drivers.ocisci] diff --git a/tests/oc-integration-tests/drone/storage-home-ocis.toml b/tests/oc-integration-tests/drone/storage-home-ocis.toml new file mode 100644 index 0000000000..098aaf7fca --- /dev/null +++ b/tests/oc-integration-tests/drone/storage-home-ocis.toml @@ -0,0 +1,47 @@ +# This config file will start a reva service that: +# - uses the ocis driver to serve users, jailed into their home (/home) +# - serves the home storage provider on grpc port 12000 +# - serves http dataprovider for this storage on port 12001 +# - /data - dataprovider: file up and download +# +# The home storage will inject the username into the path and jail users into +# their home directory + +[shared] +jwt_secret = "Pive-Fumkiu4" +gatewaysvc = "localhost:19000" + +[grpc] +address = "0.0.0.0:12000" + +# This is a storage provider that grants direct access to the wrapped storage +# the context path wrapper reads tho username from the context and prefixes the relative storage path with it +[grpc.services.storageprovider] +driver = "ocis" +mount_path = "/home" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +expose_data_server = true +data_server_url = "http://revad-services:12001/data" +enable_home_creation = true +gateway_addr = "0.0.0.0:19000" + +[grpc.services.storageprovider.drivers.ocis] +root = "/drone/src/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true +gateway_addr = "0.0.0.0:19000" + +# we have a locally running dataprovider +[http] +address = "0.0.0.0:12001" + +[http.services.dataprovider] +driver = "ocis" +temp_folder = "/drone/src/tmp/reva/tmp" + +[http.services.dataprovider.drivers.ocis] +root = "/drone/src/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true diff --git a/tests/oc-integration-tests/drone/storage-users-ocis.toml b/tests/oc-integration-tests/drone/storage-users-ocis.toml index d076b92bf1..150e1da57d 100644 --- a/tests/oc-integration-tests/drone/storage-users-ocis.toml +++ b/tests/oc-integration-tests/drone/storage-users-ocis.toml @@ -17,14 +17,14 @@ address = "0.0.0.0:11000" driver = "ocis" expose_data_server = true data_server_url = "http://revad-services:11001/data" +gateway_addr = "0.0.0.0:19000" [grpc.services.storageprovider.drivers.ocis] root = "/drone/src/tmp/reva/data" treetime_accounting = true treesize_accounting = true -permissionssvc = "localhost:10000" -personalspacealias_template = "{{.SpaceType}}/{{.User.Username}}" -generalspacealias_template = "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}" +userprovidersvc = "localhost:18000" +gateway_addr = "0.0.0.0:19000" # we have a locally running dataprovider [http] diff --git a/tests/oc-integration-tests/local/permissions-ocis-ci.toml b/tests/oc-integration-tests/local/permissions-ocis-ci.toml index 489460b82f..ef025245f3 100644 --- a/tests/oc-integration-tests/local/permissions-ocis-ci.toml +++ b/tests/oc-integration-tests/local/permissions-ocis-ci.toml @@ -9,4 +9,4 @@ address = "0.0.0.0:10000" [grpc.services.permissions] driver = "demo" -[grpc.services.permissions.drivers.demo] +[grpc.services.publicshareprovider.drivers.ocisci] diff --git a/tests/oc-integration-tests/local/storage-home.toml b/tests/oc-integration-tests/local/storage-home.toml new file mode 100644 index 0000000000..cd019d9dff --- /dev/null +++ b/tests/oc-integration-tests/local/storage-home.toml @@ -0,0 +1,51 @@ +# This config file will start a reva service that: +# - uses the ocis driver to serve users, jailed into their home (/home) +# - serves the home storage provider on grpc port 12000 +# - serves http dataprovider for this storage on port 12001 +# - /data - dataprovider: file up and download +# +# The home storage will inject the username into the path and jail users into +# their home directory + +[shared] +jwt_secret = "Pive-Fumkiu4" +gatewaysvc = "localhost:19000" + + +[grpc] +address = "0.0.0.0:12000" + +# This is a storage provider that grants direct access to the wrapped storage +# the context path wrapper reads the username from the context and prefixes the relative storage path with it +[grpc.services.storageprovider] +driver = "ocis" +mount_path = "/home" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +expose_data_server = true +data_server_url = "http://localhost:12001/data" +enable_home_creation = true + +[grpc.services.storageprovider.drivers.ocis] +root = "/var/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true +gateway_addr = "0.0.0.0:19000" +#user_layout = +# do we need owner for users? +#owner = 95cb8724-03b2-11eb-a0a6-c33ef8ef53ad + + +# we have a locally running dataprovider +[http] +address = "0.0.0.0:12001" + +[http.services.dataprovider] +driver = "ocis" +temp_folder = "/var/tmp/reva/tmp" + +[http.services.dataprovider.drivers.ocis] +root = "/var/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true diff --git a/tests/oc-integration-tests/local/storage-users.toml b/tests/oc-integration-tests/local/storage-users.toml index a270504b28..4d159eb192 100644 --- a/tests/oc-integration-tests/local/storage-users.toml +++ b/tests/oc-integration-tests/local/storage-users.toml @@ -42,4 +42,4 @@ temp_folder = "/var/tmp/reva/tmp" root = "/var/tmp/reva/data" treetime_accounting = true treesize_accounting = true -permissionssvc = "localhost:10000" +gateway_addr = "0.0.0.0:19000"