Merge branch 'master' into backmerge-master

This commit is contained in:
Michael Barz
2024-01-24 22:56:07 +01:00
218 changed files with 13283 additions and 3699 deletions

View File

@@ -16,7 +16,7 @@ OC_CI_GOLANG = "owncloudci/golang:1.21"
OC_CI_NODEJS = "owncloudci/nodejs:%s"
OC_CI_PHP = "owncloudci/php:%s"
OC_CI_WAIT_FOR = "owncloudci/wait-for:latest"
OC_CS3_API_VALIDATOR = "owncloud/cs3api-validator:0.2.0"
OC_CS3_API_VALIDATOR = "owncloud/cs3api-validator:0.2.1"
OC_LITMUS = "owncloudci/litmus:latest"
OC_OC_TEST_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.8.8"
OC_UBUNTU = "owncloud/ubuntu:20.04"
@@ -941,7 +941,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4):
[
{
"name": "wopiserver",
"image": "cs3org/wopiserver:v10.2.2",
"image": "cs3org/wopiserver:v10.3.0",
"detach": True,
"commands": [
"cp %s/tests/config/drone/wopiserver.conf /etc/wopi/wopiserver.conf" % (dirs["base"]),
@@ -2876,6 +2876,13 @@ def k6LoadTests(ctx):
"sh %s/run_k6_tests.sh --ocis-log" % (dirs["base"]),
],
},
{
"name": "open-grafana-dashboard",
"image": OC_CI_ALPINE,
"commands": [
"echo 'Grafana Dashboard: https://grafana.k6.infra.owncloud.works/d/P4D1D31A5B69203FF'",
],
},
],
"depends_on": [],
"trigger": {

View File

@@ -2,4 +2,6 @@ Enhancement: Update reva to latest edge version
We update reva to the latest edge version to get the latest fixes and features.
https://github.com/owncloud/ocis/pull/8278
https://github.com/owncloud/ocis/pull/8264
https://github.com/owncloud/ocis/pull/8100

View File

@@ -0,0 +1,5 @@
Bugfix: Cleanup graph/pkg/service/v0/driveitems.go
Main fix is using proto getters to avoid panics. But some other code improvements were also done
https://github.com/owncloud/ocis/pull/8228

View File

@@ -0,0 +1,5 @@
Bugfix: Cleanup `search/pkg/search/search.go`
Now uses proto getters to avoid panics.
https://github.com/owncloud/ocis/pull/8230

View File

@@ -0,0 +1,7 @@
Enhancement: Disable the password policy
We reworked and moved disabling the password policy logic from the reva to the ocis.
https://github.com/owncloud/ocis/pull/8152
https://github.com/cs3org/reva/pull/4453
https://github.com/owncloud/ocis/issues/7916

View File

@@ -0,0 +1,7 @@
Bugfix: Fix concurrent access to a map
We fixed the race condition that led to concurrent map access in a publicshare manager.
https://github.com/owncloud/ocis/pull/8269
https://github.com/cs3org/reva/pull/4472
https://github.com/owncloud/ocis/issues/8255

View File

@@ -0,0 +1,7 @@
Bugfix: fix PATCH/DELETE status code for drives that don't support them
Updating and Deleting the virtual drives for shares is currently not supported. Instead
of returning a generic 500 status we return a 405 response now.
https://github.com/owncloud/ocis/pull/8235
https://github.com/owncloud/ocis/issues/7881

View File

@@ -0,0 +1,5 @@
Bugfix: Fix nats authentication
Fixes nats authentication for registry/events/stores
https://github.com/owncloud/ocis/pull/8236

View File

@@ -0,0 +1,6 @@
Bugfix: Fix nats registry
The nats registry would behave badly when configuring `nats-js-kv` via envvar. Reason is the way go-micro initializes.
It took 5 developers to find the issue and the fix so the details cannot be shared here. Just accept that it is working now
https://github.com/owncloud/ocis/pull/8281

View File

@@ -0,0 +1,5 @@
Bugfix: Fix jwt config of policies service
Removes jwt config of policies service
https://github.com/owncloud/ocis/pull/7893

View File

@@ -0,0 +1,11 @@
Bugfix: graph/sharedWithMe works for shares from project spaces now
We fixed a bug in the 'graph/v1beta1/me/drive/sharedWithMe' endpoint that
caused an error response when the user received shares from project spaces.
Additionally the endpoint now behaves more graceful in cases where the
displayname of the owner or creator of a share or shared resource couldn't be
resolved.
https://github.com/owncloud/ocis/pull/8233
https://github.com/owncloud/ocis/issues/8027
https://github.com/owncloud/ocis/issues/8215

View File

@@ -0,0 +1,8 @@
Bugfix: apply role constraints when creating shares via the graph API
We fixed a bug in the graph API for creating and updating shares so that
Spaceroot specific roles like 'Manager' and 'Co-owner' can no longer be
assigned for shares on files or directories.
https://github.com/owncloud/ocis/pull/8247
https://github.com/owncloud/ocis/issues/8131

View File

@@ -164,7 +164,7 @@ services:
restart: always
wopiserver:
image: cs3org/wopiserver:${WOPISERVER_DOCKER_TAG:-v10.2.2}
image: cs3org/wopiserver:${WOPISERVER_DOCKER_TAG:-v10.3.0}
networks:
ocis-net:
entrypoint:

View File

@@ -63,9 +63,10 @@ func generateIntermediateCode(templatePath string, intermediateCodePath string,
func runIntermediateCode(intermediateCodePath string) {
fmt.Println("Running intermediate go code for " + intermediateCodePath)
defaultPath := "~/.ocis"
os.Setenv("OCIS_BASE_DATA_PATH", defaultPath)
os.Setenv("OCIS_CONFIG_DIR", path.Join(defaultPath, "config"))
defaultConfigPath := "/etc/ocis"
defaultDataPath := "/var/lib/ocis"
os.Setenv("OCIS_BASE_DATA_PATH", defaultDataPath)
os.Setenv("OCIS_CONFIG_DIR", defaultConfigPath)
out, err := exec.Command("go", "run", intermediateCodePath).Output()
if err != nil {
log.Fatal(err)

30
go.mod
View File

@@ -13,7 +13,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.9.0
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
github.com/cs3org/reva/v2 v2.18.1-0.20240104084554-e85441869c2b
github.com/cs3org/reva/v2 v2.18.1-0.20240124094635-6eec406c0be7
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
github.com/disintegration/imaging v1.6.2
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
@@ -40,7 +40,7 @@ require (
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/go-playground/validator/v10 v10.16.0
github.com/go-playground/validator/v10 v10.17.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
@@ -65,8 +65,8 @@ require (
github.com/oklog/run v1.1.0
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.0
github.com/open-policy-agent/opa v0.60.0
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.5-0.20240115110609-b018a896364e
@@ -88,13 +88,13 @@ require (
github.com/xhit/go-simple-mail/v2 v2.16.0
go-micro.dev/v4 v4.9.0
go.etcd.io/bbolt v1.3.8
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
go.opentelemetry.io/contrib/zpages v0.46.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0
go.opentelemetry.io/contrib/zpages v0.47.0
go.opentelemetry.io/otel v1.22.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/trace v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0
go.opentelemetry.io/otel/sdk v1.22.0
go.opentelemetry.io/otel/trace v1.22.0
golang.org/x/crypto v0.18.0
golang.org/x/image v0.15.0
golang.org/x/net v0.20.0
@@ -193,7 +193,7 @@ require (
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-micro/plugins/v4/events/natsjs v1.2.2-0.20231215124540-f7f8d3274bf9 // indirect
github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0 // indirect
@@ -327,17 +327,17 @@ require (
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib v1.0.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect
go.opentelemetry.io/otel/metric v1.22.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.14.0 // indirect
golang.org/x/tools v0.16.1 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect

60
go.sum
View File

@@ -1018,8 +1018,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva/v2 v2.18.1-0.20240104084554-e85441869c2b h1:Nh0SZn2MyCWC/gmV6le7e9eVzux9WWGPQ/nECgh/gyg=
github.com/cs3org/reva/v2 v2.18.1-0.20240104084554-e85441869c2b/go.mod h1:QW31Q1IQ9ZCJMFv3u8/SdHSyLfCcSVNcRbqIJj+Y+7o=
github.com/cs3org/reva/v2 v2.18.1-0.20240124094635-6eec406c0be7 h1:g7vQAbo64ziFqqhKcim3JCjDW1zqHy9imAm2HZmmK8w=
github.com/cs3org/reva/v2 v2.18.1-0.20240124094635-6eec406c0be7/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
@@ -1186,8 +1186,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
@@ -1235,8 +1235,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
@@ -1770,13 +1770,13 @@ 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.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
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.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
github.com/open-policy-agent/opa v0.60.0 h1:ZPoPt4yeNs5UXCpd/P/btpSyR8CR0wfhVoh9BOwgJNs=
github.com/open-policy-agent/opa v0.60.0/go.mod h1:aD5IK6AiLNYBjNXn7E02++yC8l4Z+bRDvgM6Ss0bBzA=
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
@@ -2089,27 +2089,27 @@ go.opentelemetry.io/contrib v1.0.0 h1:khwDCxdSspjOLmFnvMuSHd/5rPzbTx0+l6aURwtQdf
go.opentelemetry.io/contrib v1.0.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/contrib/zpages v0.46.1 h1:U8Hh84dc+vJTVgRnL+QKWtWD2iqTSKibrQ85EeQqsNg=
go.opentelemetry.io/contrib/zpages v0.46.1/go.mod h1:1Wq9YTzkhr3Jkyi/sVrasFSppVzJQcvFf2Vc2ExZd6c=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
go.opentelemetry.io/contrib/zpages v0.47.0 h1:ekpdNa2wqOvAfwZIGDIIV02zmR+z08aWPt21KrPJnaU=
go.opentelemetry.io/contrib/zpages v0.47.0/go.mod h1:rBeFA/UxnMjRlEGpmClIqzf1mCIKtl7ahjww3wsSdGs=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ=
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
@@ -2229,8 +2229,8 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -2621,8 +2621,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
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=

View File

@@ -10,7 +10,6 @@ import (
type storeOptionsKey struct{}
type expiryKey struct{}
type authKey struct{}
// StoreOptions sets the options for the underlying store
func StoreOptions(opts []store.Option) registry.Option {
@@ -31,13 +30,3 @@ func ServiceExpiry(t time.Duration) registry.Option {
o.Context = context.WithValue(o.Context, expiryKey{}, t)
}
}
// Authenticate sets the username/password for the nats connection
func Authenticate(username, password string) registry.Option {
return func(o *registry.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, authKey{}, []string{username, password})
}
}

View File

@@ -5,6 +5,8 @@ import (
"context"
"encoding/json"
"errors"
"os"
"strings"
"time"
natsjskv "github.com/go-micro/plugins/v4/store/nats-js-kv"
@@ -14,7 +16,12 @@ import (
"go-micro.dev/v4/util/cmd"
)
var _registryName = "nats-js-kv"
var (
_registryName = "nats-js-kv"
_registryAddressEnv = "MICRO_REGISTRY_ADDRESS"
_registryUsernameEnv = "MICRO_REGISTRY_AUTH_USERNAME"
_registryPasswordEnv = "MICRO_REGISTRY_AUTH_PASSWORD"
)
func init() {
cmd.DefaultRegistries[_registryName] = NewRegistry
@@ -127,18 +134,31 @@ func (n *storeregistry) String() string {
func storeOptions(opts registry.Options) []store.Option {
storeoptions := []store.Option{
store.Nodes(opts.Addrs...),
store.Database("service-registry"),
store.Table("service-registry"),
natsjskv.DefaultMemory(),
}
addr := []string{"127.0.0.1:9233"}
if len(opts.Addrs) > 0 {
addr = opts.Addrs
} else if a := strings.Split(os.Getenv(_registryAddressEnv), ","); len(a) > 0 && a[0] != "" {
addr = a
}
storeoptions = append(storeoptions, store.Nodes(addr...))
natsOptions := nats.GetDefaultOptions()
natsOptions.Name = "nats-js-kv-registry"
natsOptions.User, natsOptions.Password = getAuth()
storeoptions = append(storeoptions, natsjskv.NatsOptions(natsOptions))
if so, ok := opts.Context.Value(storeOptionsKey{}).([]store.Option); ok {
storeoptions = append(storeoptions, so...)
}
natsOptions := nats.GetDefaultOptions()
natsOptions.Name = "nats-js-kv-registry"
if auth, ok := opts.Context.Value(authKey{}).([]string); ok {
natsOptions.User = auth[0]
natsOptions.Password = auth[1]
}
return append(storeoptions, natsjskv.NatsOptions(natsOptions))
return storeoptions
}
func getAuth() (string, string) {
return os.Getenv(_registryUsernameEnv), os.Getenv(_registryPasswordEnv)
}

View File

@@ -20,10 +20,8 @@ import (
)
const (
registryEnv = "MICRO_REGISTRY"
registryAddressEnv = "MICRO_REGISTRY_ADDRESS"
registryUsernameEnv = "MICRO_REGISTRY_AUTH_USERNAME"
registryPasswordEnv = "MICRO_REGISTRY_AUTH_PASSWORD"
_registryEnv = "MICRO_REGISTRY"
_registryAddressEnv = "MICRO_REGISTRY_ADDRESS"
)
var (
@@ -64,7 +62,6 @@ func GetRegistry(opts ...Option) mRegistry.Registry {
case "natsjs", "nats-js", "nats-js-kv": // for backwards compatibility - we will stick with one of those
_reg = natsjsregistry.NewRegistry(
mRegistry.Addrs(cfg.Addresses...),
natsjsregistry.Authenticate(cfg.Username, cfg.Password),
)
case "memory":
_reg = memr.NewRegistry()
@@ -112,15 +109,13 @@ func getEnvs(opts ...Option) *Config {
cfg := &Config{
Type: "nats-js-kv",
Addresses: []string{"127.0.0.1:9233"},
Username: os.Getenv(registryUsernameEnv),
Password: os.Getenv(registryPasswordEnv),
}
if s := os.Getenv(registryEnv); s != "" {
if s := os.Getenv(_registryEnv); s != "" {
cfg.Type = s
}
if s := strings.Split(os.Getenv(registryAddressEnv), ","); len(s) > 0 && s[0] != "" {
if s := strings.Split(os.Getenv(_registryAddressEnv), ","); len(s) > 0 && s[0] != "" {
cfg.Addresses = s
}

View File

@@ -63,6 +63,8 @@ type Cache struct {
TTL time.Duration `yaml:"ttl" env:"OCIS_CACHE_TTL" desc:"Time to live for events in the store. The duration can be set as number followed by a unit identifier like s, m or h."`
Size int `yaml:"size" env:"OCIS_CACHE_SIZE" desc:"The maximum quantity of items in the store. Only applies when store type 'ocmem' is configured."`
DisablePersistence bool `yaml:"disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
AuthUsername string `yaml:"auth_username" env:"OCIS_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
AuthPassword string `yaml:"auth_password" env:"OCIS_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
}
// Commons holds configuration that are common to all extensions. Each extension can then decide whether

View File

@@ -33,7 +33,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -50,7 +50,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -32,7 +32,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -49,7 +49,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -12,13 +12,13 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/sync"
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/config"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/config/parser"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/logging"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/revaconfig"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/server/debug"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/tracing"
"github.com/urfave/cli/v2"
)
@@ -33,7 +33,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
err := tracing.Configure(cfg, logger)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -43,11 +43,16 @@ func Server(cfg *config.Config) *cli.Command {
defer cancel()
pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid")
reg := registry.GetRegistry()
rcfg := revaconfig.AuthMachineConfigFromStruct(cfg)
gr.Add(func() error {
runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger))
runtime.RunWithOptions(rcfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(traceProvider),
)
return nil
}, func(err error) {
logger.Error().

View File

@@ -24,12 +24,6 @@ type Config struct {
Supervised bool `yaml:"-"`
Context context.Context `yaml:"-"`
}
type Tracing struct {
Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED" desc:"Activates tracing."`
Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_SERVICE_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."`
Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_SERVICE_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."`
Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_SERVICE_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."`
}
type Log struct {
Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_SERVICE_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."`

View File

@@ -0,0 +1,21 @@
package config
import "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
// Tracing is the config for tracing parameters
type Tracing struct {
Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED" desc:"Activates tracing."`
Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_SERVICE_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."`
Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_SERVICE_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."`
Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_SERVICE_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."`
}
// Convert Tracing to the tracing package's Config struct.
func (t Tracing) Convert() tracing.Config {
return tracing.Config{
Enabled: t.Enabled,
Type: t.Type,
Endpoint: t.Endpoint,
Collector: t.Collector,
}
}

View File

@@ -1,25 +0,0 @@
package tracing
import (
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/auth-service/pkg/config"
"go.opentelemetry.io/otel/trace"
pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
)
var (
// TraceProvider is the global trace provider for the service.
TraceProvider = trace.NewNoopTracerProvider()
)
func Configure(cfg *config.Config, logger log.Logger) error {
var err error
if cfg.Tracing.Enabled {
if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil {
return err
}
}
return nil
}

View File

@@ -75,6 +75,8 @@ When setting the `FRONTEND_AUTO_ACCEPT_SHARES` to `true`, all incoming shares wi
Note that the password policy currently impacts only **public link password validation**.
In Infinite Scale, the password policy is always enabled because the max-length restriction is always applying and should be taken into account by the clients.
With the password policy, mandatory criteria for the password can be defined via the environment variables listed below.
Generally, a password can contain any UTF-8 characters, however some characters are regarded as special since they are not used in ordinary texts. Which characters should be treated as special is defined by "The OWASP® Foundation" [password-special-characters](https://owasp.org/www-community/password-special-characters) (between double quotes): " !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"

View File

@@ -33,7 +33,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -54,7 +54,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -137,6 +137,8 @@ type OCS struct {
StatCacheTTL time.Duration `yaml:"stat_cache_ttl" env:"OCIS_CACHE_TTL;FRONTEND_OCS_STAT_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
StatCacheSize int `yaml:"stat_cache_size" env:"OCIS_CACHE_SIZE;FRONTEND_OCS_STAT_CACHE_SIZE" desc:"Max number of entries to hold in the cache."`
StatCacheDisablePersistence bool `yaml:"stat_cache_disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE" desc:"Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false."`
StatCacheAuthUsername string `yaml:"stat_cache_auth_username" env:"OCIS_CACHE_AUTH_USERNAME;FRONTEND_OCS_STAT_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when using the 'nats-js-kv' store type."`
StatCacheAuthPassword string `yaml:"stat_cache_auth_password" env:"OCIS_CACHE_AUTH_PASSWORD;FRONTEND_OCS_STAT_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when using the 'nats-js-kv' store type."`
CacheWarmupDriver string `yaml:"cache_warmup_driver,omitempty"` // not supported by the oCIS product, therefore not part of docs
CacheWarmupDrivers CacheWarmupDrivers `yaml:"cache_warmup_drivers,omitempty"` // not supported by the oCIS product, therefore not part of docs

View File

@@ -8,7 +8,6 @@ import (
"path"
"path/filepath"
"strconv"
"time"
"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
@@ -25,14 +24,10 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
webURL.Path = path.Join(webURL.Path, "external")
webOpenInAppURL := webURL.String()
var bannedPasswordsList map[string]struct{}
if cfg.PasswordPolicy.BannedPasswordsList != "" {
bannedPasswordsList, err = readMultilineFile(cfg.PasswordPolicy.BannedPasswordsList)
if err != nil {
err = fmt.Errorf("failed to load the banned passwords from a file %s: %w", cfg.PasswordPolicy.BannedPasswordsList, err)
logger.Err(err).Send()
return nil, err
}
passwordPolicyCfg, err := passwordPolicyConfig(cfg)
if err != nil {
logger.Err(err).Send()
return nil, err
}
archivers := []map[string]interface{}{
@@ -164,9 +159,11 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
"cache_nodes": cfg.OCS.StatCacheNodes,
"cache_database": cfg.OCS.StatCacheDatabase,
"cache_table": cfg.OCS.StatCacheTable,
"cache_ttl": cfg.OCS.StatCacheTTL / time.Second,
"cache_ttl": cfg.OCS.StatCacheTTL,
"cache_size": cfg.OCS.StatCacheSize,
"cache_disable_persistence": cfg.OCS.StatCacheDisablePersistence,
"cache_auth_username": cfg.OCS.StatCacheAuthUsername,
"cache_auth_password": cfg.OCS.StatCacheAuthPassword,
},
"prefix": cfg.OCS.Prefix,
"additional_info_attribute": cfg.OCS.AdditionalInfoAttribute,
@@ -327,16 +324,7 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
},
},
},
"password_policy": map[string]interface{}{
"max_characters": 72,
"disabled": cfg.PasswordPolicy.Disabled,
"min_characters": cfg.PasswordPolicy.MinCharacters,
"min_lowercase_characters": cfg.PasswordPolicy.MinLowerCaseCharacters,
"min_uppercase_characters": cfg.PasswordPolicy.MinUpperCaseCharacters,
"min_digits": cfg.PasswordPolicy.MinDigits,
"min_special_characters": cfg.PasswordPolicy.MinSpecialCharacters,
"banned_passwords_list": bannedPasswordsList,
},
"password_policy": passwordPolicyCfg,
"notifications": map[string]interface{}{
"endpoints": []string{"list", "get", "delete"},
},
@@ -385,3 +373,30 @@ func fileExists(path string) bool {
}
return !info.IsDir()
}
func passwordPolicyConfig(cfg *config.Config) (map[string]interface{}, error) {
_maxCharacters := 72
if cfg.PasswordPolicy.Disabled {
return map[string]interface{}{
"max_characters": _maxCharacters,
"banned_passwords_list": nil,
}, nil
}
var bannedPasswordsList map[string]struct{}
var err error
if cfg.PasswordPolicy.BannedPasswordsList != "" {
bannedPasswordsList, err = readMultilineFile(cfg.PasswordPolicy.BannedPasswordsList)
if err != nil {
return nil, fmt.Errorf("failed to load the banned passwords from a file %s: %w", cfg.PasswordPolicy.BannedPasswordsList, err)
}
}
return map[string]interface{}{
"max_characters": _maxCharacters,
"min_digits": cfg.PasswordPolicy.MinDigits,
"min_characters": cfg.PasswordPolicy.MinCharacters,
"min_lowercase_characters": cfg.PasswordPolicy.MinLowerCaseCharacters,
"min_uppercase_characters": cfg.PasswordPolicy.MinUpperCaseCharacters,
"min_special_characters": cfg.PasswordPolicy.MinSpecialCharacters,
"banned_passwords_list": bannedPasswordsList,
}, nil
}

View File

@@ -85,22 +85,20 @@ type StorageRegistry struct {
// Cache holds cache config
type Cache struct {
StatCacheStore string // NOTE: The stat cache is not working atm. Hence we block configuring it
StatCacheNodes []string `yaml:"stat_cache_nodes" env:"OCIS_CACHE_STORE_NODES;GATEWAY_STAT_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' or 'ocmem' stores are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details."`
StatCacheDatabase string `yaml:"stat_cache_database" env:"OCIS_CACHE_DATABASE" desc:"The database name the configured store should use."`
StatCacheTTL time.Duration `yaml:"stat_cache_ttl" env:"OCIS_CACHE_TTL;GATEWAY_STAT_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
StatCacheSize int `yaml:"stat_cache_size" env:"OCIS_CACHE_SIZE;GATEWAY_STAT_CACHE_SIZE" desc:"The maximum quantity of items in the cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
StatCacheDisablePersistence bool `yaml:"stat_cache_disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_STAT_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the stat cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
ProviderCacheStore string `yaml:"provider_cache_store" env:"OCIS_CACHE_STORE;GATEWAY_PROVIDER_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details."`
ProviderCacheNodes []string `yaml:"provider_cache_nodes" env:"OCIS_CACHE_STORE_NODES;GATEWAY_PROVIDER_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' or 'ocmem' stores are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details."`
ProviderCacheDatabase string `yaml:"provider_cache_database" env:"OCIS_CACHE_DATABASE" desc:"The database name the configured store should use."`
ProviderCacheTTL time.Duration `yaml:"provider_cache_ttl" env:"OCIS_CACHE_TTL;GATEWAY_PROVIDER_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
ProviderCacheSize int `yaml:"provider_cache_size" env:"OCIS_CACHE_SIZE;GATEWAY_PROVIDER_CACHE_SIZE" desc:"The maximum quantity of items in the cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
ProviderCacheDisablePersistence bool `yaml:"provider_cache_disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_PROVIDER_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the provider cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
ProviderCacheAuthUsername string `yaml:"provider_cache_auth_username" env:"OCIS_CACHE_AUTH_USERNAME;GATEWAY_PROVIDER_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
ProviderCacheAuthPassword string `yaml:"provider_cache_auth_password" env:"OCIS_CACHE_AUTH_PASSWORD;GATEWAY_PROVIDER_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
CreateHomeCacheStore string `yaml:"create_home_cache_store" env:"OCIS_CACHE_STORE;GATEWAY_CREATE_HOME_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details."`
CreateHomeCacheNodes []string `yaml:"create_home_cache_nodes" env:"OCIS_CACHE_STORE_NODES;GATEWAY_CREATE_HOME_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' or 'ocmem' stores are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details."`
CreateHomeCacheDatabase string `yaml:"create_home_cache_database" env:"OCIS_CACHE_DATABASE" desc:"The database name the configured store should use."`
CreateHomeCacheTTL time.Duration `yaml:"create_home_cache_ttl" env:"OCIS_CACHE_TTL;GATEWAY_CREATE_HOME_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
CreateHomeCacheSize int `yaml:"create_home_cache_size" env:"OCIS_CACHE_SIZE;GATEWAY_CREATE_HOME_CACHE_SIZE" desc:"The maximum quantity of items in the cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
CreateHomeCacheDisablePersistence bool `yaml:"create_home_cache_disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
CreateHomeCacheAuthUsername string `yaml:"create_home_cache_auth_username" env:"OCIS_CACHE_AUTH_USERNAME;GATEWAY_CREATE_HOME_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
CreateHomeCacheAuthPassword string `yaml:"create_home_cache_auth_password" env:"OCIS_CACHE_AUTH_PASSWORD;GATEWAY_CREATE_HOME_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured."`
}

View File

@@ -39,10 +39,6 @@ func DefaultConfig() *config.Config {
DisableHomeCreationOnLogin: true,
TransferExpires: 24 * 60 * 60,
Cache: config.Cache{
StatCacheStore: "noop", // NOTE: stat cache not working
StatCacheDatabase: "ocis",
StatCacheNodes: []string{"127.0.0.1:9233"},
StatCacheTTL: 300 * time.Second,
ProviderCacheStore: "noop",
ProviderCacheNodes: []string{"127.0.0.1:9233"},
ProviderCacheDatabase: "cache-providers",

View File

@@ -61,15 +61,6 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
"transfer_shared_secret": cfg.TransferSecret,
"transfer_expires": cfg.TransferExpires,
// cache and TTLs
"stat_cache_config": map[string]interface{}{
"cache_store": cfg.Cache.StatCacheStore,
"cache_nodes": cfg.Cache.StatCacheNodes,
"cache_database": cfg.Cache.StatCacheDatabase,
"cache_table": "stat",
"cache_ttl": cfg.Cache.StatCacheTTL,
"cache_size": cfg.Cache.StatCacheSize,
"cache_disable_persistenc": cfg.Cache.StatCacheDisablePersistence,
},
"provider_cache_config": map[string]interface{}{
"cache_store": cfg.Cache.ProviderCacheStore,
"cache_nodes": cfg.Cache.ProviderCacheNodes,
@@ -78,6 +69,8 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
"cache_ttl": cfg.Cache.ProviderCacheTTL,
"cache_size": cfg.Cache.ProviderCacheSize,
"disable_persistence": cfg.Cache.ProviderCacheDisablePersistence,
"cache_auth_username": cfg.Cache.ProviderCacheAuthUsername,
"cache_auth_password": cfg.Cache.ProviderCacheAuthPassword,
},
"create_home_cache_config": map[string]interface{}{
"cache_store": cfg.Cache.CreateHomeCacheStore,
@@ -87,6 +80,8 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
"cache_ttl": cfg.Cache.CreateHomeCacheTTL,
"cache_size": cfg.Cache.CreateHomeCacheSize,
"cache_disable_persistence": cfg.Cache.CreateHomeCacheDisablePersistence,
"cache_auth_username": cfg.Cache.CreateHomeCacheAuthUsername,
"cache_auth_password": cfg.Cache.CreateHomeCacheAuthPassword,
},
},
"authregistry": map[string]interface{}{

View File

@@ -2,6 +2,7 @@ package svc
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -41,7 +42,6 @@ import (
"github.com/owncloud/ocis/v2/services/graph/pkg/validate"
)
// From https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0
// CreateUploadSession create an upload session to allow your app to upload files up to the maximum file size.
// An upload session allows your app to upload ranges of the file in sequential API requests, which allows the
// transfer to be resumed if a connection is dropped while the upload is in progress.
@@ -55,6 +55,7 @@ import (
// }
//
// ```
// From https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0
func (g Graph) CreateUploadSession(w http.ResponseWriter, r *http.Request) {
g.logger.Info().Msg("Calling CreateUploadSession")
@@ -68,7 +69,7 @@ func (g Graph) CreateUploadSession(w http.ResponseWriter, r *http.Request) {
errorcode.RenderError(w, r, err)
return
}
if driveID.StorageId != driveItemID.StorageId || driveID.SpaceId != driveItemID.SpaceId {
if driveID.GetStorageId() != driveItemID.GetStorageId() || driveID.GetSpaceId() != driveItemID.GetSpaceId() {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
return
}
@@ -101,27 +102,27 @@ func (g Graph) CreateUploadSession(w http.ResponseWriter, r *http.Request) {
case err != nil:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
case res.Status.Code == cs3rpc.Code_CODE_OK:
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_OK:
// ok
case res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage())
return
case res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
case res.Status.Code == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
default:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
return
}
uploadSession := uploadSession{
CS3Protocols: res.Protocols,
CS3Protocols: res.GetProtocols(),
}
for _, p := range res.Protocols {
if p.Protocol == "simple" {
uploadSession.UploadUrl = p.UploadEndpoint + "/" + p.Token
for _, p := range res.GetProtocols() {
if p.GetProtocol() == "simple" {
uploadSession.UploadURL = p.GetUploadEndpoint() + "/" + p.GetToken()
}
}
render.Status(r, http.StatusOK)
@@ -129,18 +130,18 @@ func (g Graph) CreateUploadSession(w http.ResponseWriter, r *http.Request) {
}
type createUploadSessionRequest struct {
DeferCommit bool
Item driveItemUploadableProperties
DeferCommit bool `json:"deferCommit"`
Item driveItemUploadableProperties `json:"item"`
}
type driveItemUploadableProperties struct {
// ConflictBehavior "@microsoft.graph.conflictBehavior"
//Description string
FileSize int64
FileSize int64 `json:"fileSize"`
// fileSystemInfo
Name string
Name string `json:"name"`
}
type uploadSession struct {
UploadUrl string
UploadURL string
//"expirationDateTime": "2015-01-29T09:21:55.523Z",
//"nextExpectedRanges": ["0-"]
CS3Protocols []*gateway.FileUploadProtocol
@@ -161,7 +162,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
currentUser := revactx.ContextMustGetUser(r.Context())
// do we need to list all or only the personal drive
filters := []*storageprovider.ListStorageSpacesRequest_Filter{}
filters = append(filters, listStorageSpacesUserFilter(currentUser.GetId().OpaqueId))
filters = append(filters, listStorageSpacesUserFilter(currentUser.GetId().GetOpaqueId()))
filters = append(filters, listStorageSpacesTypeFilter("personal"))
res, err := gatewayClient.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{
@@ -172,47 +173,47 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
g.logger.Error().Err(err).Msg("error making ListStorageSpaces grpc call")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
return
case res.Status.Code != cs3rpc.Code_CODE_OK:
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
case res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK:
if res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage())
return
}
g.logger.Error().Err(err).Msg("error sending ListStorageSpaces grpc request")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
return
}
var space *storageprovider.StorageSpace
for _, s := range res.StorageSpaces {
for _, s := range res.GetStorageSpaces() {
if utils.UserIDEqual(currentUser.GetId(), s.GetOwner().GetId()) {
space = s
}
}
lRes, err := gatewayClient.ListContainer(ctx, &storageprovider.ListContainerRequest{
Ref: &storageprovider.Reference{ResourceId: space.Root},
Ref: &storageprovider.Reference{ResourceId: space.GetRoot()},
})
switch {
case err != nil:
g.logger.Error().Err(err).Msg("error making ListContainer grpc call")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
return
case lRes.Status.Code != cs3rpc.Code_CODE_OK:
if lRes.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, lRes.Status.Message)
case lRes.GetStatus().GetCode() != cs3rpc.Code_CODE_OK:
if lRes.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, lRes.GetStatus().GetMessage())
return
}
if lRes.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED {
if lRes.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED {
// TODO check if we should return 404 to not disclose existing items
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, lRes.Status.Message)
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, lRes.GetStatus().GetMessage())
return
}
g.logger.Error().Err(err).Msg("error sending list container grpc request")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
return
}
files, err := formatDriveItems(g.logger, lRes.Infos)
files, err := formatDriveItems(g.logger, lRes.GetInfos())
if err != nil {
g.logger.Error().Err(err).Msg("error encoding response as json")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -238,7 +239,7 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
errorcode.RenderError(w, r, err)
return
}
if driveID.StorageId != driveItemID.StorageId || driveID.SpaceId != driveItemID.SpaceId {
if driveID.GetStorageId() != driveItemID.GetStorageId() || driveID.GetSpaceId() != driveItemID.GetSpaceId() {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
return
}
@@ -262,22 +263,22 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
case err != nil:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
case res.Status.Code == cs3rpc.Code_CODE_OK:
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_OK:
// ok
case res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage())
return
case res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
case res.Status.Code == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
default:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
return
}
driveItem, err := cs3ResourceToDriveItem(g.logger, res.Info)
driveItem, err := cs3ResourceToDriveItem(g.logger, res.GetInfo())
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
@@ -302,7 +303,7 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
errorcode.RenderError(w, r, err)
return
}
if driveID.StorageId != driveItemID.StorageId || driveID.SpaceId != driveItemID.SpaceId {
if driveID.GetStorageId() != driveItemID.GetStorageId() || driveID.GetSpaceId() != driveItemID.GetSpaceId() {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
return
}
@@ -329,23 +330,23 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
case err != nil:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
case res.Status.Code == cs3rpc.Code_CODE_OK:
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_OK:
// ok
case res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage())
return
case res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
case res.Status.Code == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.Status.Message) // do not leak existence? check what graph does
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_UNAUTHENTICATED:
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.GetStatus().GetMessage()) // do not leak existence? check what graph does
return
default:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
return
}
files, err := formatDriveItems(g.logger, res.Infos)
files, err := formatDriveItems(g.logger, res.GetInfos())
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
@@ -372,11 +373,16 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
g.logger.Warn().Err(errCode).Interface("stat.res", statResponse)
g.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
errCode.Render(w, r)
return
}
condition := unifiedrole.UnifiedRoleConditionGrantee
if IsSpaceRoot(statResponse.GetInfo().GetId()) {
condition = unifiedrole.UnifiedRoleConditionOwner
}
permissionSet := *statResponse.GetInfo().GetPermissionSet()
allowedActions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(permissionSet)
@@ -385,7 +391,7 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
LibreGraphPermissionsRolesAllowedValues: conversions.ToValueSlice(
unifiedrole.GetApplicableRoleDefinitionsForActions(
allowedActions,
unifiedrole.UnifiedRoleConditionGrantee,
condition,
g.config.FilesSharing.EnableResharing,
false,
),
@@ -451,28 +457,39 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
return
}
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
g.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
errCode.Render(w, r)
return
}
condition := unifiedrole.UnifiedRoleConditionGrantee
if IsSpaceRoot(statResponse.GetInfo().GetId()) {
condition = unifiedrole.UnifiedRoleConditionOwner
}
unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: driveItemInvite.LibreGraphPermissionsActions}}
for _, roleId := range driveItemInvite.GetRoles() {
role, err := unifiedrole.NewUnifiedRoleFromID(roleId, g.config.FilesSharing.EnableResharing)
for _, roleID := range driveItemInvite.GetRoles() {
role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing)
if err != nil {
g.logger.Debug().Err(err).Interface("role", driveItemInvite.GetRoles()[0]).Msg("unable to convert requested role")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
}
allowedResourceActions := unifiedrole.GetAllowedResourceActions(role, condition)
if len(allowedResourceActions) == 0 {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "role not applicable to this resource")
return
}
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
g.logger.Warn().Err(errCode).Interface("stat.res", statResponse)
errCode.Render(w, r)
return
unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
}
driveRecipient := driveItemInvite.GetRecipients()[0]
objectId := driveRecipient.GetObjectId()
objectID := driveRecipient.GetObjectId()
cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions)
createShareRequest := &collaboration.CreateShareRequest{
@@ -485,7 +502,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
}
permission := &libregraph.Permission{}
if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, unifiedrole.UnifiedRoleConditionGrantee, g.config.FilesSharing.EnableResharing); role != nil {
if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, g.config.FilesSharing.EnableResharing); role != nil {
permission.Roles = []string{role.GetId()}
}
@@ -495,9 +512,9 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
switch driveRecipient.GetLibreGraphRecipientType() {
case "group":
group, err := g.identityCache.GetGroup(ctx, objectId)
group, err := g.identityCache.GetGroup(ctx, objectID)
if err != nil {
g.logger.Debug().Err(err).Interface("groupId", objectId).Msg("failed group lookup")
g.logger.Debug().Err(err).Interface("groupId", objectID).Msg("failed group lookup")
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
return
}
@@ -514,9 +531,9 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
},
}
default:
user, err := g.identityCache.GetUser(ctx, objectId)
user, err := g.identityCache.GetUser(ctx, objectID)
if err != nil {
g.logger.Debug().Err(err).Interface("userId", objectId).Msg("failed user lookup")
g.logger.Debug().Err(err).Interface("userId", objectID).Msg("failed user lookup")
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
return
}
@@ -588,7 +605,7 @@ func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) {
return
}
oldPermission, sharedResourceId, err := g.getPermissionByID(ctx, permissionID)
oldPermission, sharedResourceID, err := g.getPermissionByID(ctx, permissionID)
if err != nil {
errorcode.RenderError(w, r, err)
return
@@ -596,7 +613,7 @@ func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) {
// The resourceID of the shared resource need to match the item ID from the Request Path
// otherwise this is an invalid Request.
if !utils.ResourceIDEqual(sharedResourceId, &itemID) {
if !utils.ResourceIDEqual(sharedResourceID, &itemID) {
g.logger.Debug().Msg("resourceID of shared does not match itemID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match")
return
@@ -615,14 +632,13 @@ func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) {
}
// This is a user share
updatedPermission, err := g.updateUserShare(ctx, permissionID, oldPermission, permission)
updatedPermission, err := g.updateUserShare(ctx, permissionID, permission)
if err != nil {
errorcode.RenderError(w, r, err)
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &updatedPermission)
return
}
// DeletePermission removes a Permission from a Drive item
@@ -643,13 +659,13 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
isUserPermission := true
// Check if the id is refering to a User Share
sharedResourceId, err := g.getUserPermissionResourceID(ctx, permissionID)
// Check if the id is referring to a User Share
sharedResourceID, err := g.getUserPermissionResourceID(ctx, permissionID)
var errcode errorcode.Error
if err != nil && errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound {
// there is no user share with that ID, so lets check if it is referring to a public link
isUserPermission = false
sharedResourceId, err = g.getLinkPermissionResourceID(ctx, permissionID)
sharedResourceID, err = g.getLinkPermissionResourceID(ctx, permissionID)
}
if err != nil {
@@ -659,7 +675,7 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
// The resourceID of the shared resource need to match the item ID from the Request Path
// otherwise this is an invalid Request.
if !utils.ResourceIDEqual(sharedResourceId, &itemID) {
if !utils.ResourceIDEqual(sharedResourceID, &itemID) {
g.logger.Debug().Msg("resourceID of shared does not match itemID")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match")
return
@@ -679,7 +695,6 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
return
}
func (g Graph) getPermissionByID(ctx context.Context, permissionID string) (*libregraph.Permission, *storageprovider.ResourceId, error) {
@@ -741,7 +756,7 @@ func (g Graph) getCS3UserShareByID(ctx context.Context, permissionID string) (*c
return getShareResp.GetShare(), nil
}
func (g Graph) updateUserShare(ctx context.Context, permissionID string, oldPermission, newPermission *libregraph.Permission) (*libregraph.Permission, error) {
func (g Graph) updateUserShare(ctx context.Context, permissionID string, newPermission *libregraph.Permission) (*libregraph.Permission, error) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
@@ -765,8 +780,8 @@ func (g Graph) updateUserShare(ctx context.Context, permissionID string, oldPerm
var roles, allowedResourceActions []string
var permissionsUpdated, ok bool
if roles, ok = newPermission.GetRolesOk(); ok && len(roles) > 0 {
for _, roleId := range roles {
role, err := unifiedrole.NewUnifiedRoleFromID(roleId, g.config.FilesSharing.EnableResharing)
for _, roleID := range roles {
role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing)
if err != nil {
g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role")
return nil, err
@@ -906,11 +921,11 @@ func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference)
if err != nil {
return nil, err
}
if res.Status.Code != cs3rpc.Code_CODE_OK {
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
refStr, _ := storagespace.FormatReference(&ref)
return nil, fmt.Errorf("could not stat %s: %s", refStr, res.Status.Message)
return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage())
}
return cs3ResourceToDriveItem(g.logger, res.Info)
return cs3ResourceToDriveItem(g.logger, res.GetInfo())
}
func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.ResourceId, baseURL *url.URL) (*libregraph.RemoteItem, error) {
@@ -926,12 +941,12 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource
if err != nil {
return nil, err
}
if res.Status.Code != cs3rpc.Code_CODE_OK {
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
// Only log this, there could be mountpoints which have no grant
g.logger.Debug().Msg(res.Status.Message)
g.logger.Debug().Msg(res.GetStatus().GetMessage())
return nil, errors.New("could not fetch grant resource for the mountpoint")
}
item, err := cs3ResourceToRemoteItem(res.Info)
item, err := cs3ResourceToRemoteItem(res.GetInfo())
if err != nil {
return nil, err
}
@@ -965,46 +980,46 @@ func formatDriveItems(logger *log.Logger, mds []*storageprovider.ResourceInfo) (
}
func cs3TimestampToTime(t *types.Timestamp) time.Time {
return time.Unix(int64(t.Seconds), int64(t.Nanos))
return time.Unix(int64(t.GetSeconds()), int64(t.GetNanos()))
}
func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
size := new(int64)
*size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64
*size = int64(res.GetSize()) // TODO lurking overflow: make size of libregraph drive item use uint64
driveItem := &libregraph.DriveItem{
Id: libregraph.PtrString(storagespace.FormatResourceID(*res.Id)),
Id: libregraph.PtrString(storagespace.FormatResourceID(*res.GetId())),
Size: size,
}
if name := path.Base(res.Path); name != "" {
if name := path.Base(res.GetPath()); name != "" {
driveItem.Name = &name
}
if res.Etag != "" {
if res.GetEtag() != "" {
driveItem.ETag = &res.Etag
}
if res.Mtime != nil {
lastModified := cs3TimestampToTime(res.Mtime)
if res.GetMtime() != nil {
lastModified := cs3TimestampToTime(res.GetMtime())
driveItem.LastModifiedDateTime = &lastModified
}
if res.ParentId != nil {
if res.GetParentId() != nil {
parentRef := libregraph.NewItemReference()
parentRef.SetDriveType(res.Space.SpaceType)
parentRef.SetDriveId(storagespace.FormatStorageID(res.ParentId.StorageId, res.ParentId.SpaceId))
parentRef.SetId(storagespace.FormatResourceID(*res.ParentId))
parentRef.SetDriveType(res.GetSpace().GetSpaceType())
parentRef.SetDriveId(storagespace.FormatStorageID(res.GetParentId().GetStorageId(), res.GetParentId().GetSpaceId()))
parentRef.SetId(storagespace.FormatResourceID(*res.GetParentId()))
driveItem.ParentReference = parentRef
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE && res.MimeType != "" {
if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_FILE && res.GetMimeType() != "" {
// We cannot use a libregraph.File here because the openapi codegenerator autodetects 'File' as a go type ...
driveItem.File = &libregraph.OpenGraphFile{
MimeType: &res.MimeType,
}
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
driveItem.Folder = &libregraph.Folder{}
}
if res.ArbitraryMetadata != nil {
if res.GetArbitraryMetadata() != nil {
driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
driveItem.Location = cs3ResourceToDriveItemLocationFacet(logger, res)
}
@@ -1013,11 +1028,11 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf
}
func cs3ResourceToDriveItemAudioFacet(logger *log.Logger, res *storageprovider.ResourceInfo) *libregraph.Audio {
if !strings.HasPrefix(res.MimeType, "audio/") {
if !strings.HasPrefix(res.GetMimeType(), "audio/") {
return nil
}
k := res.ArbitraryMetadata.Metadata
k := res.GetArbitraryMetadata().GetMetadata()
if k == nil {
return nil
}
@@ -1031,7 +1046,7 @@ func cs3ResourceToDriveItemAudioFacet(logger *log.Logger, res *storageprovider.R
}
func cs3ResourceToDriveItemLocationFacet(logger *log.Logger, res *storageprovider.ResourceInfo) *libregraph.GeoCoordinates {
k := res.ArbitraryMetadata.Metadata
k := res.GetArbitraryMetadata().GetMetadata()
if k == nil {
return nil
}
@@ -1099,30 +1114,30 @@ func unmarshalStringMap(logger *log.Logger, out any, flatMap map[string]string,
func cs3ResourceToRemoteItem(res *storageprovider.ResourceInfo) (*libregraph.RemoteItem, error) {
size := new(int64)
*size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64
*size = int64(res.GetSize()) // TODO lurking overflow: make size of libregraph drive item use uint64
remoteItem := &libregraph.RemoteItem{
Id: libregraph.PtrString(storagespace.FormatResourceID(*res.Id)),
Id: libregraph.PtrString(storagespace.FormatResourceID(*res.GetId())),
Size: size,
}
if res.GetPath() != "" {
remoteItem.Path = libregraph.PtrString(path.Clean(res.GetPath()))
}
if res.Etag != "" {
if res.GetEtag() != "" {
remoteItem.ETag = &res.Etag
}
if res.Mtime != nil {
lastModified := cs3TimestampToTime(res.Mtime)
if res.GetMtime() != nil {
lastModified := cs3TimestampToTime(res.GetMtime())
remoteItem.LastModifiedDateTime = &lastModified
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE && res.MimeType != "" {
if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_FILE && res.GetMimeType() != "" {
// We cannot use a libregraph.File here because the openapi codegenerator autodetects 'File' as a go type ...
remoteItem.File = &libregraph.OpenGraphFile{
MimeType: &res.MimeType,
}
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
remoteItem.Folder = &libregraph.Folder{}
}
if res.GetSpace() != nil && res.GetSpace().GetRoot() != nil {
@@ -1145,10 +1160,10 @@ func (g Graph) getPathForResource(ctx context.Context, id storageprovider.Resour
if err != nil {
return "", err
}
if res.Status.Code != cs3rpc.Code_CODE_OK {
return "", fmt.Errorf("could not stat %v: %s", id, res.Status.Message)
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
return "", fmt.Errorf("could not stat %v: %s", id, res.GetStatus().GetMessage())
}
return res.Path, err
return res.GetPath(), err
}
// getSpecialDriveItems reads properties from the opaque and transforms them into driveItems
@@ -1156,20 +1171,20 @@ func (g Graph) getSpecialDriveItems(ctx context.Context, baseURL *url.URL, space
if space.GetRoot().GetStorageId() == utils.ShareStorageProviderID {
return nil // no point in stating the ShareStorageProvider
}
if space.Opaque == nil {
if space.GetOpaque() == nil {
return nil
}
imageNode := utils.ReadPlainFromOpaque(space.Opaque, SpaceImageSpecialFolderName)
readmeNode := utils.ReadPlainFromOpaque(space.Opaque, ReadmeSpecialFolderName)
imageNode := utils.ReadPlainFromOpaque(space.GetOpaque(), SpaceImageSpecialFolderName)
readmeNode := utils.ReadPlainFromOpaque(space.GetOpaque(), ReadmeSpecialFolderName)
cachekey := spaceRootStatKey(space.Root, imageNode, readmeNode)
cachekey := spaceRootStatKey(space.GetRoot(), imageNode, readmeNode)
// if the root is older or equal to our cache we can reuse the cached extended spaces properties
if entry := g.specialDriveItemsCache.Get(cachekey); entry != nil {
if cached, ok := entry.Value().(specialDriveItemEntry); ok {
if cached.rootMtime != nil && space.Mtime != nil {
if cached.rootMtime != nil && space.GetMtime() != nil {
// beware, LaterTS does not handle equalness. it returns t1 if t1 > t2, else t2, so a >= check looks like this
if utils.LaterTS(space.Mtime, cached.rootMtime) == cached.rootMtime {
if utils.LaterTS(space.GetMtime(), cached.rootMtime) == cached.rootMtime {
return cached.specialDriveItems
}
}
@@ -1184,7 +1199,7 @@ func (g Graph) getSpecialDriveItems(ctx context.Context, baseURL *url.URL, space
// cache properties
spacePropertiesEntry := specialDriveItemEntry{
specialDriveItems: spaceItems,
rootMtime: space.Mtime,
rootMtime: space.GetMtime(),
}
g.specialDriveItemsCache.Set(cachekey, spacePropertiesEntry, time.Duration(g.config.Spaces.ExtendedSpacePropertiesCacheTTL))
@@ -1222,7 +1237,7 @@ func spaceRootStatKey(id *storageprovider.ResourceId, imagenode, readmeNode stri
_, _ = shakeHash.Write([]byte(readmeNode))
h := make([]byte, 64)
_, _ = shakeHash.Read(h)
return fmt.Sprintf("%x", h)
return hex.EncodeToString(h)
}
type specialDriveItemEntry struct {
@@ -1244,10 +1259,10 @@ func (g Graph) getSpecialDriveItem(ctx context.Context, ref storageprovider.Refe
g.logger.Debug().Err(err).Str("ID", ref.GetResourceId().GetOpaqueId()).Str("name", itemName).Msg("Could not get item info")
return nil
}
itemPath := ref.Path
itemPath := ref.GetPath()
if itemPath == "" {
// lookup by id
itemPath, err = g.getPathForResource(ctx, *ref.ResourceId)
itemPath, err = g.getPathForResource(ctx, *ref.GetResourceId())
if err != nil {
g.logger.Debug().Err(err).Str("ID", ref.GetResourceId().GetOpaqueId()).Str("name", itemName).Msg("Could not get item path")
return nil
@@ -1255,7 +1270,7 @@ func (g Graph) getSpecialDriveItem(ctx context.Context, ref storageprovider.Refe
}
spaceItem.SpecialFolder = &libregraph.SpecialFolder{Name: libregraph.PtrString(itemName)}
webdavURL := *baseURL
webdavURL.Path = path.Join(webdavURL.Path, space.Id.OpaqueId, itemPath)
webdavURL.Path = path.Join(webdavURL.Path, space.GetId().GetOpaqueId(), itemPath)
spaceItem.WebDavUrl = libregraph.PtrString(webdavURL.String())
return spaceItem

View File

@@ -685,6 +685,25 @@ var _ = Describe("Driveitems", func() {
_, ok := res.GetRolesOk()
Expect(ok).To(BeTrue())
})
It("fails to update the share permissions for a file share when setting a space specific role", func() {
updateShareMock := gatewayClient.On("UpdateShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
return req.GetShare().GetId().GetOpaqueId() == "permissionid"
}),
)
updateShareMock.Return(updateShareMockResponse, nil)
driveItemPermission.SetRoles([]string{unifiedrole.NewSpaceViewerUnifiedRole().GetId()})
body, err := driveItemPermission.MarshalJSON()
Expect(err).To(BeNil())
svc.UpdatePermission(
rr,
httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("updates the share permissions when changing the resource permission actions", func() {
updateShareMock := gatewayClient.On("UpdateShare",
mock.Anything,
@@ -1007,6 +1026,17 @@ var _ = Describe("Driveitems", func() {
Expect(jsonData.Get("0.roles.0").String()).To(Equal(unifiedrole.NewViewerUnifiedRole(true).GetId()))
})
It("fails with wrong role", func() {
driveItemInvite.Roles = []string{unifiedrole.NewCoownerUnifiedRole().GetId()}
svc.Invite(
rr,
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("with actions (happy path)", func() {
driveItemInvite.Roles = nil
driveItemInvite.LibreGraphPermissionsActions = []string{unifiedrole.DriveItemContentRead}

View File

@@ -622,6 +622,10 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
logger.Debug().Interface("id", rid).Msg("could not update drive, invalid argument")
errorcode.NotAllowed.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage())
return
case cs3rpc.Code_CODE_UNIMPLEMENTED:
logger.Debug().Interface("id", rid).Msg("could not delete drive: delete not implemented for this type of drive")
errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "drive cannot be updated")
return
default:
logger.Debug().Interface("id", rid).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not update drive: grpc error")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "grpc error")
@@ -1193,6 +1197,10 @@ func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) {
logger.Debug().Interface("id", rid).Msg("could not delete drive: drive not found")
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
return
case cs3rpc.Code_CODE_UNIMPLEMENTED:
logger.Debug().Interface("id", rid).Msg("could not delete drive: delete not implemented for this type of drive")
errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "drive cannot be deleted")
return
// don't expose internal error codes to the outside world
default:
logger.Debug().Str("grpc", dRes.GetStatus().GetMessage()).Interface("id", rid).Msg("could not delete drive: grpc error")

View File

@@ -6,6 +6,7 @@ import (
"reflect"
"slices"
cs3User "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/utils"
@@ -183,55 +184,36 @@ func (g Graph) listSharedWithMe(ctx context.Context) ([]libregraph.DriveItem, er
}
if userID := shareStat.GetInfo().GetOwner(); userID != nil {
user, err := g.identityCache.GetUser(ctx, userID.GetOpaqueId())
identity, err := g.cs3UserIdToIdentity(ctx, userID)
if err != nil {
g.logger.Error().Err(err).Msg("could not get user")
return err
// TODO: define a proper error behavior here. We don't
// want the whole request to fail just because a single
// resource owner couldn't be resolved. But, should be
// really return the affect share in the response?
// For now we just log a warning. The returned
// identitySet will just contain the userid.
g.logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get owner of shared resource")
}
identitySet := libregraph.IdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: libregraph.PtrString(user.GetId()),
},
}
remoteItem.SetCreatedBy(identitySet)
driveItem.SetCreatedBy(identitySet)
remoteItem.SetCreatedBy(libregraph.IdentitySet{User: &identity})
driveItem.SetCreatedBy(libregraph.IdentitySet{User: &identity})
}
if userID := receivedShare.GetShare().GetOwner(); userID != nil {
user, err := g.identityCache.GetUser(ctx, userID.GetOpaqueId())
identity, err := g.cs3UserIdToIdentity(ctx, userID)
if err != nil {
g.logger.Error().Err(err).Msg("could not get user")
return err
g.logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get owner of the share")
}
identitySet := libregraph.IdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: libregraph.PtrString(user.GetId()),
},
}
shared.SetOwner(identitySet)
shared.SetOwner(libregraph.IdentitySet{User: &identity})
}
if userID := receivedShare.GetShare().GetCreator(); userID != nil {
user, err := g.identityCache.GetUser(ctx, userID.GetOpaqueId())
identity, err := g.cs3UserIdToIdentity(ctx, userID)
if err != nil {
g.logger.Error().Err(err).Msg("could not get user")
return err
g.logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get creator of the share")
}
identitySet := libregraph.IdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: libregraph.PtrString(user.GetId()),
},
}
shared.SetSharedBy(identitySet)
shared.SetSharedBy(libregraph.IdentitySet{User: &identity})
}
@@ -344,3 +326,18 @@ func (g Graph) cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, rece
return permission, nil
}
func (g Graph) cs3UserIdToIdentity(ctx context.Context, cs3UserID *cs3User.UserId) (libregraph.Identity, error) {
identity := libregraph.Identity{
Id: libregraph.PtrString(cs3UserID.GetOpaqueId()),
}
var err error
if cs3UserID.GetType() != cs3User.UserType_USER_TYPE_SPACE_OWNER {
var user libregraph.User
user, err = g.identityCache.GetUser(ctx, cs3UserID.GetOpaqueId())
if err == nil {
identity.SetDisplayName(user.GetDisplayName())
}
}
return identity, err
}

View File

@@ -393,5 +393,35 @@ var _ = Describe("SharedWithMe", func() {
Expect(jsonData.Get("user.displayName").String()).To(Equal(shareCreator.DisplayName))
Expect(jsonData.Get("user.id").String()).To(Equal(shareCreator.Id.OpaqueId))
})
It("returns shares created on project space", func() {
shareCreator := getUserResponseDefault.User
ownerID := &userv1beta1.UserId{
OpaqueId: "project-space-id",
Type: userv1beta1.UserType_USER_TYPE_SPACE_OWNER,
}
share := listReceivedSharesResponse.Shares[0].Share
share.Creator = shareCreator.Id
share.Owner = ownerID
resourceInfo := statResponse.Info
resourceInfo.Owner = ownerID
svc.ListSharedWithMe(
tape,
httptest.NewRequest(http.MethodGet, "/graph/v1beta1/me/drive/sharedWithMe", nil),
)
jsonData := gjson.Get(tape.Body.String(), "value.0.createdBy")
Expect(jsonData.Get("user.displayName").String()).To(Equal(""))
Expect(jsonData.Get("user.id").String()).To(Equal(ownerID.OpaqueId))
jsonData = gjson.Get(tape.Body.String(), "value.0.remoteItem.shared")
Expect(jsonData.Get("sharedBy.user.displayName").String()).To(Equal(shareCreator.DisplayName))
Expect(jsonData.Get("sharedBy.user.id").String()).To(Equal(shareCreator.Id.OpaqueId))
Expect(jsonData.Get("owner.user.displayName").String()).To(Equal(""))
Expect(jsonData.Get("owner.user.id").String()).To(Equal(ownerID.OpaqueId))
})
})
})

View File

@@ -153,7 +153,7 @@ func NewCoownerUnifiedRole() *libregraph.UnifiedRoleDefinition {
RolePermissions: []libregraph.UnifiedRolePermission{
{
AllowedResourceActions: convert(r),
Condition: proto.String(UnifiedRoleConditionGrantee),
Condition: proto.String(UnifiedRoleConditionOwner),
},
},
LibreGraphWeight: proto.Int32(0),
@@ -185,10 +185,6 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition {
Description: proto.String("Grants manager permissions on a resource. Semantically equivalent to co-owner"),
DisplayName: displayName(r),
RolePermissions: []libregraph.UnifiedRolePermission{
{
AllowedResourceActions: convert(r),
Condition: proto.String(UnifiedRoleConditionGrantee),
},
{
AllowedResourceActions: convert(r),
Condition: proto.String(UnifiedRoleConditionOwner),

View File

@@ -27,8 +27,7 @@ var _ = Describe("unifiedroles", func() {
Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
@@ -208,6 +207,17 @@ var _ = Describe("unifiedroles", func() {
unifiedrole.NewViewerUnifiedRole(false),
unifiedrole.NewFileEditorUnifiedRole(false),
unifiedrole.NewEditorUnifiedRole(false),
},
),
Entry(
"GetBuiltinRoleDefinitionList",
rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList(false)...),
unifiedrole.UnifiedRoleConditionOwner,
false,
[]*libregraph.UnifiedRoleDefinition{
unifiedrole.NewSpaceViewerUnifiedRole(),
unifiedrole.NewSpaceEditorUnifiedRole(),
unifiedrole.NewCoownerUnifiedRole(),
unifiedrole.NewManagerUnifiedRole(),
},
@@ -223,8 +233,6 @@ var _ = Describe("unifiedroles", func() {
unifiedrole.NewViewerUnifiedRole(true),
unifiedrole.NewFileEditorUnifiedRole(true),
unifiedrole.NewEditorUnifiedRole(true),
unifiedrole.NewCoownerUnifiedRole(),
unifiedrole.NewManagerUnifiedRole(),
},
),

View File

@@ -34,7 +34,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -63,7 +63,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-04-19 11:11+0000\n"
"Last-Translator: Michael Barz <mbarz@owncloud.com>, 2023\n"
"Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-04-19 11:11+0000\n"
"Last-Translator: Michael Barz <mbarz@owncloud.com>, 2023\n"
"Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-04-19 11:11+0000\n"
"Last-Translator: Shouyuan, 2023\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/owncloud-org/teams/6149/zh-Hans/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-04-19 11:11+0000\n"
"Last-Translator: Shouyuan, 2023\n"
"Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n"

View File

@@ -30,7 +30,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -86,7 +86,7 @@ func Server(cfg *config.Config) *cli.Command {
ocdav.MetricsEnabled(true),
ocdav.MetricsNamespace("ocis"),
ocdav.Tracing("Adding these strings is a workaround for ->", "https://github.com/cs3org/reva/issues/4131"),
ocdav.WithTraceProvider(tracingProvider),
ocdav.WithTraceProvider(traceProvider),
}
s, err := ocdav.Service(opts...)

View File

@@ -34,11 +34,6 @@ type GRPC struct {
TLS *shared.GRPCServiceTLS `yaml:"tls"`
}
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;POLICIES_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."`
}
// Engine configures the policy engine.
type Engine struct {
Timeout time.Duration `yaml:"timeout" env:"POLICIES_ENGINE_TIMEOUT" desc:"Sets the timeout the rego expression evaluation can take. Rules default to deny if the timeout was reached. See the Environment Variable Types description for more details."`

View File

@@ -182,7 +182,7 @@ The following metrics are exposed by the proxy service:
| `ocis_proxy_build_info{version}` | A metric with a constant `1` value labeled by version, exposing the version of the ocis proxy service. | `version`: Build version of the proxy |
### Prometheus Configuration
The following is an example prometheus configuration for the single process mode. It assumes that the proxy service is configured to bind on all interfaces `PROXY_HTTP_ADDR=0.0.0.0:9205` and that the proxy is available via the `ocis` service name (typically in docker-compose). The prometheus service detects the `/metrics` endpoint automatically and scrapes it every 15 seconds.
The following is an example prometheus configuration for the single process mode. It assumes that the proxy debug address is configured to bind on all interfaces `PROXY_DEBUG_ADDR=0.0.0.0:9205` and that the proxy is available via the `ocis` service name (typically in docker-compose). The prometheus service detects the `/metrics` endpoint automatically and scrapes it every 15 seconds.
```yaml
global:

View File

@@ -32,9 +32,9 @@ func ResolveReference(ctx context.Context, ref *provider.Reference, ri *provider
}
gpRes, err := gatewayClient.GetPath(ctx, &provider.GetPathRequest{
ResourceId: ri.Id,
ResourceId: ri.GetId(),
})
if err != nil || gpRes.Status.Code != rpc.Code_CODE_OK {
if err != nil || gpRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
return nil, err
}
return &provider.Reference{
@@ -43,7 +43,7 @@ func ResolveReference(ctx context.Context, ref *provider.Reference, ri *provider
SpaceId: ref.GetResourceId().GetSpaceId(),
OpaqueId: ref.GetResourceId().GetSpaceId(),
},
Path: utils.MakeRelativePath(gpRes.Path),
Path: utils.MakeRelativePath(gpRes.GetPath()),
}, nil
}
@@ -56,7 +56,7 @@ func (ma matchArray) Swap(i, j int) {
ma[i], ma[j] = ma[j], ma[i]
}
func (ma matchArray) Less(i, j int) bool {
return ma[i].Score > ma[j].Score
return ma[i].GetScore() > ma[j].GetScore()
}
func logDocCount(engine engine.Engine, logger log.Logger) {
@@ -88,7 +88,7 @@ func statResource(ctx context.Context, ref *provider.Reference, gatewaySelector
logger.Error().Err(err).Msg("failed to stat the moved resource")
return nil, err
}
switch res.Status.Code {
switch res.GetStatus().GetCode() {
case rpc.Code_CODE_OK:
return res, nil
case rpc.Code_CODE_NOT_FOUND:
@@ -111,34 +111,34 @@ func convertToWebDAVPermissions(isShared, isMountpoint, isDir bool, p *provider.
if isShared {
fmt.Fprintf(&b, "S")
}
if p.ListContainer &&
p.ListFileVersions &&
p.ListRecycle &&
p.Stat &&
p.GetPath &&
p.GetQuota &&
p.InitiateFileDownload {
if p.GetListContainer() &&
p.GetListFileVersions() &&
p.GetListRecycle() &&
p.GetStat() &&
p.GetGetPath() &&
p.GetGetQuota() &&
p.GetInitiateFileDownload() {
fmt.Fprintf(&b, "R")
}
if isMountpoint {
fmt.Fprintf(&b, "M")
}
if p.Delete {
if p.GetDelete() {
fmt.Fprintf(&b, "D")
}
if p.InitiateFileUpload &&
p.RestoreFileVersion &&
p.RestoreRecycleItem {
if p.GetInitiateFileUpload() &&
p.GetRestoreFileVersion() &&
p.GetRestoreRecycleItem() {
fmt.Fprintf(&b, "NV")
if !isDir {
fmt.Fprintf(&b, "W")
}
}
if isDir &&
p.ListContainer &&
p.Stat &&
p.CreateContainer &&
p.InitiateFileUpload {
p.GetListContainer() &&
p.GetStat() &&
p.GetCreateContainer() &&
p.GetInitiateFileUpload() {
fmt.Fprintf(&b, "CK")
}
return b.String()

View File

@@ -32,12 +32,12 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
cfg.GrpcClient, err = ogrpc.NewClient(
append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(tracingProvider))...,
append(ogrpc.GetClientOptions(cfg.GRPCClientTLS), ogrpc.WithTraceProvider(traceProvider))...,
)
if err != nil {
return err
@@ -65,7 +65,7 @@ func Server(cfg *config.Config) *cli.Command {
http.Config(cfg),
http.Metrics(mtrcs),
http.ServiceHandler(handle),
http.TraceProvider(tracingProvider),
http.TraceProvider(traceProvider),
)
if err != nil {
logger.Error().
@@ -87,7 +87,7 @@ func Server(cfg *config.Config) *cli.Command {
grpc.Config(cfg),
grpc.Metrics(mtrcs),
grpc.ServiceHandler(handle),
grpc.TraceProvider(tracingProvider),
grpc.TraceProvider(traceProvider),
)
servers.Add(grpcServer.Run, func(_ error) {
logger.Info().Str("server", "grpc").Msg("Shutting down server")

View File

@@ -34,7 +34,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -66,7 +66,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -153,6 +153,8 @@ type Events struct {
TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;SHARING_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."`
TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false."`
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
AuthUsername string `yaml:"auth_username" env:"OCIS_EVENTS_AUTH_USERNAME;SHARING_EVENTS_AUTH_USERNAME" desc:"Username for the events broker."`
AuthPassword string `yaml:"auth_password" env:"OCIS_EVENTS_AUTH_PASSWORD;SHARING_EVENTS_AUTH_PASSWORD" desc:"Password for the events broker."`
}
// PasswordPolicy configures reva password policy

View File

@@ -14,15 +14,10 @@ import (
// SharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]interface{}, error) {
var bannedPasswordsList map[string]struct{}
var err error
if cfg.PasswordPolicy.BannedPasswordsList != "" {
bannedPasswordsList, err = readMultilineFile(cfg.PasswordPolicy.BannedPasswordsList)
if err != nil {
err = fmt.Errorf("failed to load the banned passwords from a file %s: %w", cfg.PasswordPolicy.BannedPasswordsList, err)
logger.Err(err).Send()
return nil, err
}
passwordPolicyCfg, err := passwordPolicyConfig(cfg)
if err != nil {
logger.Err(err).Send()
return nil, err
}
rcfg := map[string]interface{}{
"shared": map[string]interface{}{
@@ -86,6 +81,8 @@ func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]
"natsclusterid": cfg.Events.ClusterID,
"tlsinsecure": cfg.Events.TLSInsecure,
"tlsrootcacertificate": cfg.Events.TLSRootCaCertPath,
"authusername": cfg.Events.AuthUsername,
"authpassword": cfg.Events.AuthPassword,
},
},
},
@@ -94,16 +91,8 @@ func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]
"gateway_addr": cfg.Reva.Address,
"writeable_share_must_have_password": cfg.WriteableShareMustHavePassword,
"public_share_must_have_password": cfg.PublicShareMustHavePassword,
"password_policy": map[string]interface{}{
"disabled": cfg.PasswordPolicy.Disabled,
"min_digits": cfg.PasswordPolicy.MinDigits,
"min_characters": cfg.PasswordPolicy.MinCharacters,
"min_lowercase_characters": cfg.PasswordPolicy.MinLowerCaseCharacters,
"min_uppercase_characters": cfg.PasswordPolicy.MinUpperCaseCharacters,
"min_special_characters": cfg.PasswordPolicy.MinSpecialCharacters,
"banned_passwords_list": bannedPasswordsList,
},
"driver": cfg.PublicSharingDriver,
"password_policy": passwordPolicyCfg,
"driver": cfg.PublicSharingDriver,
"drivers": map[string]interface{}{
"json": map[string]interface{}{
"file": cfg.PublicSharingDrivers.JSON.File,
@@ -147,6 +136,8 @@ func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]
"tls-root-ca-cert": cfg.Events.TLSRootCaCertPath,
"enable-tls": cfg.Events.EnableTLS,
"name": "sharing-eventsmiddleware",
"username": cfg.Events.AuthUsername,
"password": cfg.Events.AuthPassword,
},
"prometheus": map[string]interface{}{
"namespace": "ocis",
@@ -185,3 +176,30 @@ func fileExists(path string) bool {
}
return !info.IsDir()
}
func passwordPolicyConfig(cfg *config.Config) (map[string]interface{}, error) {
_maxCharacters := 72
if cfg.PasswordPolicy.Disabled {
return map[string]interface{}{
"max_characters": _maxCharacters,
"banned_passwords_list": nil,
}, nil
}
var bannedPasswordsList map[string]struct{}
var err error
if cfg.PasswordPolicy.BannedPasswordsList != "" {
bannedPasswordsList, err = readMultilineFile(cfg.PasswordPolicy.BannedPasswordsList)
if err != nil {
return nil, fmt.Errorf("failed to load the banned passwords from a file %s: %w", cfg.PasswordPolicy.BannedPasswordsList, err)
}
}
return map[string]interface{}{
"max_characters": _maxCharacters,
"min_digits": cfg.PasswordPolicy.MinDigits,
"min_characters": cfg.PasswordPolicy.MinCharacters,
"min_lowercase_characters": cfg.PasswordPolicy.MinLowerCaseCharacters,
"min_uppercase_characters": cfg.PasswordPolicy.MinUpperCaseCharacters,
"min_special_characters": cfg.PasswordPolicy.MinSpecialCharacters,
"banned_passwords_list": bannedPasswordsList,
}, nil
}

View File

@@ -33,7 +33,7 @@ func Server(cfg *config.Config) *cli.Command {
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
tracingProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
if err != nil {
return err
}
@@ -50,7 +50,7 @@ func Server(cfg *config.Config) *cli.Command {
runtime.RunWithOptions(rCfg, pidFile,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(reg),
runtime.WithTraceProvider(tracingProvider),
runtime.WithTraceProvider(traceProvider),
)
return nil

View File

@@ -93,4 +93,6 @@ type Cache struct {
TTL time.Duration `yaml:"ttl" env:"OCIS_CACHE_TTL;STORAGE_SYSTEM_CACHE_TTL" desc:"Default time to live for user info in the user info cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
Size int `yaml:"size" env:"OCIS_CACHE_SIZE;STORAGE_SYSTEM_CACHE_SIZE" desc:"The maximum quantity of items in the user info cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
DisablePersistence bool `yaml:"disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_SYSTEM_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
AuthUsername string `yaml:"auth_username" env:"OCIS_CACHE_AUTH_USERNAME;STORAGE_SYSTEM_CACHE_AUTH_USERNAME" desc:"Username for the configured store. Only applies when store type 'nats-js-kv' is configured."`
AuthPassword string `yaml:"auth_password" env:"OCIS_CACHE_AUTH_PASSWORD;STORAGE_SYSTEM_CACHE_AUTH_PASSWORD" desc:"Password for the configured store. Only applies when store type 'nats-js-kv' is configured."`
}

View File

@@ -1,8 +1,6 @@
package revaconfig
import (
"time"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/owncloud/ocis/v2/services/storage-system/pkg/config"
)
@@ -165,9 +163,11 @@ func metadataDrivers(cfg *config.Config) map[string]interface{} {
"cache_store": cfg.FileMetadataCache.Store,
"cache_nodes": cfg.FileMetadataCache.Nodes,
"cache_database": cfg.FileMetadataCache.Database,
"cache_ttl": cfg.FileMetadataCache.TTL / time.Second,
"cache_ttl": cfg.FileMetadataCache.TTL,
"cache_size": cfg.FileMetadataCache.Size,
"cache_disable_persistence": cfg.FileMetadataCache.DisablePersistence,
"cache_auth_username": cfg.FileMetadataCache.AuthUsername,
"cache_auth_password": cfg.FileMetadataCache.AuthPassword,
},
},
}

View File

@@ -31,7 +31,6 @@ type Config struct {
TransferExpires int64 `yaml:"transfer_expires" env:"STORAGE_USERS_TRANSFER_EXPIRES" desc:"the time after which the token for upload postprocessing expires"`
Events Events `yaml:"events"`
StatCache StatCache `yaml:"stat_cache"`
FilemetadataCache FilemetadataCache `yaml:"filemetadata_cache"`
IDCache IDCache `yaml:"id_cache"`
MountID string `yaml:"mount_id" env:"STORAGE_USERS_MOUNT_ID" desc:"Mount ID of this storage."`
@@ -182,16 +181,6 @@ type Events struct {
AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;STORAGE_USERS_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
}
// StatCache holds cache config
type StatCache struct {
Store string `yaml:"store" env:"OCIS_CACHE_STORE;STORAGE_USERS_STAT_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details."`
Nodes []string `yaml:"nodes" env:"OCIS_CACHE_STORE_NODES;STORAGE_USERS_STAT_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' or 'ocmem' stores are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details."`
Database string `yaml:"database" env:"OCIS_CACHE_DATABASE" desc:"The database name the configured store should use."`
TTL time.Duration `yaml:"ttl" env:"OCIS_CACHE_TTL;STORAGE_USERS_STAT_CACHE_TTL" desc:"Default time to live for user info in the user info cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
Size int `yaml:"size" env:"OCIS_CACHE_SIZE;STORAGE_USERS_STAT_CACHE_SIZE" desc:"The maximum quantity of items in the user info cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
DisablePersistence bool `yaml:"disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_STAT_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
}
// FilemetadataCache holds cache config
type FilemetadataCache struct {
Store string `yaml:"store" env:"OCIS_CACHE_STORE;STORAGE_USERS_FILEMETADATA_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details."`
@@ -200,6 +189,8 @@ type FilemetadataCache struct {
TTL time.Duration `yaml:"ttl" env:"OCIS_CACHE_TTL;STORAGE_USERS_FILEMETADATA_CACHE_TTL" desc:"Default time to live for user info in the user info cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details."`
Size int `yaml:"size" env:"OCIS_CACHE_SIZE;STORAGE_USERS_FILEMETADATA_CACHE_SIZE" desc:"The maximum quantity of items in the user info cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
DisablePersistence bool `yaml:"disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_FILEMETADATA_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
AuthUsername string `yaml:"username" env:"OCIS_CACHE_AUTH_USERNAME;STORAGE_USERS_FILEMETADATA_CACHE_AUTH_USERNAME" desc:"The username to authenticate with the cache store. Only applies when store type 'nats-js-kv' is configured."`
AuthPassword string `yaml:"password" env:"OCIS_CACHE_AUTH_PASSWORD;STORAGE_USERS_FILEMETADATA_CACHE_AUTH_PASSWORD" desc:"The password to authenticate with the cache store. Only applies when store type 'nats-js-kv' is configured."`
}
// IDCache holds cache config
@@ -210,6 +201,8 @@ type IDCache struct {
TTL time.Duration `yaml:"ttl" env:"OCIS_CACHE_TTL;STORAGE_USERS_ID_CACHE_TTL" desc:"Default time to live for user info in the user info cache. Only applied when access tokens have no expiration. Defaults to 300s which is derived from the underlaying package though not explicitly set as default. See the Environment Variable Types description for more details."`
Size int `yaml:"size" env:"OCIS_CACHE_SIZE;STORAGE_USERS_ID_CACHE_SIZE" desc:"The maximum quantity of items in the user info cache. Only applies when store type 'ocmem' is configured. Defaults to 512 which is derived from the ocmem package though not exclicitely set as default."`
DisablePersistence bool `yaml:"disable_persistence" env:"OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_ID_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false."`
AuthUsername string `yaml:"username" env:"OCIS_CACHE_AUTH_USERNAME;STORAGE_USERS_ID_CACHE_AUTH_USERNAME" desc:"The username to authenticate with the cache store. Only applies when store type 'nats-js-kv' is configured."`
AuthPassword string `yaml:"password" env:"OCIS_CACHE_AUTH_PASSWORD;STORAGE_USERS_ID_CACHE_AUTH_PASSWORD" desc:"The password to authenticate with the cache store. Only applies when store type 'nats-js-kv' is configured."`
}
// S3Driver is the storage driver configuration when using 's3' storage driver

View File

@@ -94,12 +94,6 @@ func DefaultConfig() *config.Config {
ClusterID: "ocis-cluster",
EnableTLS: false,
},
StatCache: config.StatCache{
Store: "memory",
Nodes: []string{"127.0.0.1:9233"},
Database: "ocis",
TTL: 300 * time.Second,
},
FilemetadataCache: config.FilemetadataCache{
Store: "memory",
Nodes: []string{"127.0.0.1:9233"},

View File

@@ -46,6 +46,8 @@ func StorageUsersConfigFromStruct(cfg *config.Config) map[string]interface{} {
"tls-root-ca-cert": cfg.Events.TLSRootCaCertPath,
"enable-tls": cfg.Events.EnableTLS,
"name": "storage-users-eventsmiddleware",
"username": cfg.Events.AuthUsername,
"password": cfg.Events.AuthPassword,
},
"prometheus": map[string]interface{}{
"namespace": "ocis",
@@ -70,35 +72,8 @@ func StorageUsersConfigFromStruct(cfg *config.Config) map[string]interface{} {
"nats_tls_insecure": cfg.Events.TLSInsecure,
"nats_root_ca_cert_path": cfg.Events.TLSRootCaCertPath,
"nats_enable_tls": cfg.Events.EnableTLS,
"data_txs": map[string]interface{}{
"simple": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_table": "stat",
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"spaces": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_table": "stat",
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"tus": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_table": "stat",
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
},
"nats_username": cfg.Events.AuthUsername,
"nats_password": cfg.Events.AuthPassword,
},
},
},

View File

@@ -132,14 +132,6 @@ func Ocis(cfg *config.Config) map[string]interface{} {
"max_concurrency": cfg.Drivers.OCIS.MaxConcurrency,
"asyncfileuploads": cfg.Drivers.OCIS.AsyncUploads,
"max_quota": cfg.Drivers.OCIS.MaxQuota,
"statcache": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"filemetadatacache": map[string]interface{}{
"cache_store": cfg.FilemetadataCache.Store,
"cache_nodes": cfg.FilemetadataCache.Nodes,
@@ -147,6 +139,8 @@ func Ocis(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.FilemetadataCache.TTL,
"cache_size": cfg.FilemetadataCache.Size,
"cache_disable_persistence": cfg.FilemetadataCache.DisablePersistence,
"cache_auth_username": cfg.FilemetadataCache.AuthUsername,
"cache_auth_password": cfg.FilemetadataCache.AuthPassword,
},
"idcache": map[string]interface{}{
"cache_store": cfg.IDCache.Store,
@@ -155,13 +149,11 @@ func Ocis(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.IDCache.TTL,
"cache_size": cfg.IDCache.Size,
"cache_disable_persistence": cfg.IDCache.DisablePersistence,
"cache_auth_username": cfg.IDCache.AuthUsername,
"cache_auth_password": cfg.IDCache.AuthPassword,
},
"events": map[string]interface{}{
"natsaddress": cfg.Events.Addr,
"natsclusterid": cfg.Events.ClusterID,
"tlsinsecure": cfg.Events.TLSInsecure,
"tlsrootcacertificate": cfg.Events.TLSRootCaCertPath,
"numconsumers": cfg.Events.NumConsumers,
"numconsumers": cfg.Events.NumConsumers,
},
"tokens": map[string]interface{}{
"transfer_shared_secret": cfg.Commons.TransferSecret,
@@ -193,14 +185,6 @@ func OcisNoEvents(cfg *config.Config) map[string]interface{} {
"lock_cycle_duration_factor": cfg.Drivers.OCIS.LockCycleDurationFactor,
"max_concurrency": cfg.Drivers.OCIS.MaxConcurrency,
"max_quota": cfg.Drivers.OCIS.MaxQuota,
"statcache": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"filemetadatacache": map[string]interface{}{
"cache_store": cfg.FilemetadataCache.Store,
"cache_nodes": cfg.FilemetadataCache.Nodes,
@@ -208,6 +192,8 @@ func OcisNoEvents(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.FilemetadataCache.TTL,
"cache_size": cfg.FilemetadataCache.Size,
"cache_disable_persistence": cfg.FilemetadataCache.DisablePersistence,
"cache_auth_username": cfg.FilemetadataCache.AuthUsername,
"cache_auth_password": cfg.FilemetadataCache.AuthPassword,
},
"idcache": map[string]interface{}{
"cache_store": cfg.IDCache.Store,
@@ -216,6 +202,8 @@ func OcisNoEvents(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.IDCache.TTL,
"cache_size": cfg.IDCache.Size,
"cache_disable_persistence": cfg.IDCache.DisablePersistence,
"cache_auth_username": cfg.IDCache.AuthUsername,
"cache_auth_password": cfg.IDCache.AuthPassword,
},
}
}
@@ -259,14 +247,6 @@ func S3NG(cfg *config.Config) map[string]interface{} {
"lock_cycle_duration_factor": cfg.Drivers.S3NG.LockCycleDurationFactor,
"max_concurrency": cfg.Drivers.S3NG.MaxConcurrency,
"asyncfileuploads": cfg.Drivers.OCIS.AsyncUploads,
"statcache": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"filemetadatacache": map[string]interface{}{
"cache_store": cfg.FilemetadataCache.Store,
"cache_nodes": cfg.FilemetadataCache.Nodes,
@@ -274,6 +254,8 @@ func S3NG(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.FilemetadataCache.TTL,
"cache_size": cfg.FilemetadataCache.Size,
"cache_disable_persistence": cfg.FilemetadataCache.DisablePersistence,
"cache_auth_username": cfg.FilemetadataCache.AuthUsername,
"cache_auth_password": cfg.FilemetadataCache.AuthPassword,
},
"idcache": map[string]interface{}{
"cache_store": cfg.IDCache.Store,
@@ -282,13 +264,11 @@ func S3NG(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.IDCache.TTL,
"cache_size": cfg.IDCache.Size,
"cache_disable_persistence": cfg.IDCache.DisablePersistence,
"cache_auth_username": cfg.IDCache.AuthUsername,
"cache_auth_password": cfg.IDCache.AuthPassword,
},
"events": map[string]interface{}{
"natsaddress": cfg.Events.Addr,
"natsclusterid": cfg.Events.ClusterID,
"tlsinsecure": cfg.Events.TLSInsecure,
"tlsrootcacertificate": cfg.Events.TLSRootCaCertPath,
"numconsumers": cfg.Events.NumConsumers,
"numconsumers": cfg.Events.NumConsumers,
},
"tokens": map[string]interface{}{
"transfer_shared_secret": cfg.Commons.TransferSecret,
@@ -324,14 +304,6 @@ func S3NGNoEvents(cfg *config.Config) map[string]interface{} {
"max_acquire_lock_cycles": cfg.Drivers.S3NG.MaxAcquireLockCycles,
"max_concurrency": cfg.Drivers.S3NG.MaxConcurrency,
"lock_cycle_duration_factor": cfg.Drivers.S3NG.LockCycleDurationFactor,
"statcache": map[string]interface{}{
"cache_store": cfg.StatCache.Store,
"cache_nodes": cfg.StatCache.Nodes,
"cache_database": cfg.StatCache.Database,
"cache_ttl": cfg.StatCache.TTL,
"cache_size": cfg.StatCache.Size,
"cache_disable_persistence": cfg.StatCache.DisablePersistence,
},
"filemetadatacache": map[string]interface{}{
"cache_store": cfg.FilemetadataCache.Store,
"cache_nodes": cfg.FilemetadataCache.Nodes,
@@ -339,6 +311,8 @@ func S3NGNoEvents(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.FilemetadataCache.TTL,
"cache_size": cfg.FilemetadataCache.Size,
"cache_disable_persistence": cfg.FilemetadataCache.DisablePersistence,
"cache_auth_username": cfg.FilemetadataCache.AuthUsername,
"cache_auth_password": cfg.FilemetadataCache.AuthPassword,
},
"idcache": map[string]interface{}{
"cache_store": cfg.IDCache.Store,
@@ -347,6 +321,8 @@ func S3NGNoEvents(cfg *config.Config) map[string]interface{} {
"cache_ttl": cfg.IDCache.TTL,
"cache_size": cfg.IDCache.Size,
"cache_disable_persistence": cfg.IDCache.DisablePersistence,
"cache_auth_username": cfg.IDCache.AuthUsername,
"cache_auth_password": cfg.IDCache.AuthPassword,
},
}
}

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-03-15 08:28+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>, 2023\n"
"Language-Team: English (United Kingdom) (https://app.transifex.com/owncloud-org/teams/6149/en_GB/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2024-01-03 04:25+0000\n"
"POT-Creation-Date: 2024-01-24 00:44+0000\n"
"PO-Revision-Date: 2023-03-15 08:28+0000\n"
"Last-Translator: Juan Carlos Garrote, 2023\n"
"Language-Team: Spanish (https://app.transifex.com/owncloud-org/teams/6149/es/)\n"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-28 00:05+0000\n"
"POT-Creation-Date: 2024-01-18 00:47+0000\n"
"PO-Revision-Date: 2023-03-15 08:28+0000\n"
"Last-Translator: Roman Perekhod, 2023\n"
"Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2023-12-30 00:38+0000\n"
"POT-Creation-Date: 2024-01-19 00:48+0000\n"
"PO-Revision-Date: 2023-03-15 08:28+0000\n"
"Last-Translator: Begüm Topyıldız <bgmtpyldz@gmail.com>, 2023\n"
"Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n"

View File

@@ -1822,4 +1822,29 @@ class GraphHelper {
self::getRequestHeaders()
);
}
/**
* @param string $baseUrl
* @param string $xRequestId
* @param string $user
* @param string $password
*
* @return ResponseInterface
* @throws GuzzleException
*/
public static function getSharesSharedWithMe(
string $baseUrl,
string $xRequestId,
string $user,
string $password
): ResponseInterface {
$url = self::getBetaFullUrl($baseUrl, "me/drive/sharedWithMe");
return HttpRequestHelper::get(
$url,
$xRequestId,
$user,
$password,
self::getRequestHeaders()
);
}
}

View File

@@ -179,6 +179,7 @@ class WebDavHelper {
* @param string|null $type
* @param int|null $davPathVersionToUse
* @param string|null $doDavRequestAsUser
* @param array|null $headers
*
* @return ResponseInterface
* @throws Exception
@@ -194,7 +195,8 @@ class WebDavHelper {
?string $folderDepth = '1',
?string $type = "files",
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
?string $doDavRequestAsUser = null
?string $doDavRequestAsUser = null,
?array $headers = []
):ResponseInterface {
$body = self::getBodyForPropfind($properties);
$folderDepth = (string) $folderDepth;

View File

@@ -125,10 +125,14 @@ cannot share a folder with create permission
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L13)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L121)
#### [Set quota over settings](https://github.com/owncloud/ocis/issues/1290)
#### [d:quota-available-bytes in dprop of PROPFIND give wrong response value](https://github.com/owncloud/ocis/issues/8197)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:91](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L91)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:101](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L101)
- [coreApiWebdavProperties/getQuota.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L55)
- [coreApiWebdavProperties/getQuota.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L56)
- [coreApiWebdavProperties/getQuota.feature:57](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L57)
- [coreApiWebdavProperties/getQuota.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L71)
- [coreApiWebdavProperties/getQuota.feature:72](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L72)
- [coreApiWebdavProperties/getQuota.feature:73](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L73)
#### [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)
@@ -144,11 +148,6 @@ cannot share a folder with create permission
- [coreApiTrashbin/trashbinSharingToShares.feature:201](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L201)
- [coreApiTrashbin/trashbinSharingToShares.feature:224](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L224)
#### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247)
- [coreApiShareOperationsToShares2/uploadToShare.feature:202](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature#L202)
- [coreApiShareOperationsToShares2/uploadToShare.feature:203](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/uploadToShare.feature#L203)
#### [Expiration date for shares is not implemented](https://github.com/owncloud/ocis/issues/1250)
#### Expiration date of user shares

View File

@@ -45,18 +45,6 @@ The expected failures in this file are from features in the owncloud/ocis repo.
- [apiGraphUserGroup/deleteGroup.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraphUserGroup/deleteGroup.feature#L67)
#### [CORS headers are not identical with oC10 headers](https://github.com/owncloud/ocis/issues/5195)
- [apiCors/cors.feature:28](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L28)
- [apiCors/cors.feature:29](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L29)
- [apiCors/cors.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L30)
- [apiCors/cors.feature:31](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L31)
#### [Requests with invalid credentials do not return CORS headers](https://github.com/owncloud/ocis/issues/5194)
- [apiCors/cors.feature:70](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L70)
- [apiCors/cors.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiCors/cors.feature#L71)
#### [A User can get information of another user with Graph API](https://github.com/owncloud/ocis/issues/5125)
- [apiGraphUserGroup/getUser.feature:89](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L89)
@@ -270,10 +258,10 @@ The expected failures in this file are from features in the owncloud/ocis repo.
- [apiSharingNg/linkShare.feature:453](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L453)
- [apiSharingNg/linkShare.feature:455](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L455)
- [apiSharingNg/linkShare.feature:456](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L456)
- [apiSharingNg/deletePermissions.feature:146](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L146)
- [apiSharingNg/deletePermissions.feature:163](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L163)
- [apiSharingNg/deletePermissions.feature:184](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L184)
- [apiSharingNg/deletePermissions.feature:203](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L203)
- [apiSharingNg/deletePermissions.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L130)
- [apiSharingNg/deletePermissions.feature:147](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L147)
- [apiSharingNg/deletePermissions.feature:168](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L168)
- [apiSharingNg/deletePermissions.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L187)
### [sharee (editor role) MOVE a file by file-id into same shared folder returns 403](https://github.com/owncloud/ocis/issues/7617)

View File

@@ -16,7 +16,9 @@ Feature: Copy test
Scenario: check the COPY response headers
Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
And user "Alice" has created a folder "new" in space "new-space"
When user "Alice" copies file "testfile.txt" from space "new-space" to "/new/testfile.txt" inside space "new-space" using the WebDAV API
When user "Alice" copies file "testfile.txt" from space "new-space" to "/new/testfile.txt" inside space "new-space" with following headers using the WebDAV API
| header | value |
| Origin | %base_url% |
Then the HTTP status code should be "201"
And the following headers should match these regular expressions
| Oc-Fileid | /^[a-f0-9!\$\-]{110}$/ |

View File

@@ -18,11 +18,10 @@ Feature: CORS headers
Then the OCS status code should be "<ocs-code>"
And the HTTP status code should be "<http-code>"
And the following headers should be set
| header | value |
| Access-Control-Allow-Headers | OC-Checksum,OC-Total-Length,OCS-APIREQUEST,X-OC-Mtime,OC-RequestAppPassword,Accept,Authorization,Brief,Content-Length,Content-Range,Content-Type,Date,Depth,Destination,Host,If,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Location,Lock-Token,Overwrite,Prefer,Range,Schedule-Reply,Timeout,User-Agent,X-Expected-Entity-Length,Accept-Language,Access-Control-Request-Method,Access-Control-Allow-Origin,Cache-Control,ETag,OC-Autorename,OC-CalDav-Import,OC-Chunked,OC-Etag,OC-FileId,OC-LazyOps,OC-Total-File-Length,Origin,X-Request-ID,X-Requested-With |
| Access-Control-Expose-Headers | Content-Location,DAV,ETag,Link,Lock-Token,OC-ETag,OC-Checksum,OC-FileId,OC-JobStatus-Location,OC-RequestAppPassword,Vary,Webdav-Location,X-Sabre-Status |
| Access-Control-Allow-Origin | https://aphno.badal |
| Access-Control-Allow-Methods | GET,OPTIONS,POST,PUT,DELETE,MKCOL,PROPFIND,PATCH,PROPPATCH,REPORT |
| header | value |
| Access-Control-Expose-Headers | Location |
| Access-Control-Allow-Origin | https://aphno.badal |
| Access-Control-Allow-Credentials | true |
Examples:
| ocs_api_version | endpoint | ocs-code | http-code |
| 1 | /config | 100 | 200 |
@@ -34,8 +33,8 @@ Feature: CORS headers
Scenario Outline: CORS headers should not be returned when CORS domain does not match origin header
Given using OCS API version "<ocs_api_version>"
When user "Alice" sends HTTP method "GET" to OCS API endpoint "<endpoint>" with headers
| header | value |
| Origin | https://mero.badal |
| header | value |
| Origin | https://mero.badal |
Then the OCS status code should be "<ocs-code>"
And the HTTP status code should be "<http-code>"
And the following headers should not be set
@@ -52,20 +51,42 @@ Feature: CORS headers
| 2 | /apps/files_sharing/api/v1/shares | 200 | 200 |
@issue-5194
Scenario Outline: CORS headers should be returned when an invalid password is used
Scenario Outline: CORS headers should be returned when an preflight request is sent
Given using OCS API version "<ocs_api_version>"
When user "Alice" sends HTTP method "GET" to OCS API endpoint "<endpoint>" with headers using password "invalid"
When user "Alice" sends HTTP method "OPTIONS" to OCS API endpoint "<endpoint>" with headers
| header | value |
| Origin | https://aphno.badal |
| Access-Control-Request-Headers | Origin, Accept, Content-Type, Depth, Authorization, Ocs-Apirequest, If-None-Match, If-Match, Destination, Overwrite, X-Request-Id, X-Requested-With, Tus-Resumable, Tus-Checksum-Algorithm, Upload-Concat, Upload-Length, Upload-Metadata, Upload-Defer-Length, Upload-Expires, Upload-Checksum, Upload-Offset, X-Http-Method-Override, Cache-Control |
| Access-Control-Request-Method | <request_method> |
And the HTTP status code should be "204"
And the following headers should be set
| header | value |
| Access-Control-Allow-Headers | Origin, Accept, Content-Type, Depth, Authorization, Ocs-Apirequest, If-None-Match, If-Match, Destination, Overwrite, X-Request-Id, X-Requested-With, Tus-Resumable, Tus-Checksum-Algorithm, Upload-Concat, Upload-Length, Upload-Metadata, Upload-Defer-Length, Upload-Expires, Upload-Checksum, Upload-Offset, X-Http-Method-Override, Cache-Control |
| Access-Control-Allow-Origin | https://aphno.badal |
| Access-Control-Allow-Methods | <request_method> |
Examples:
| ocs_api_version | | endpoint | request_method |
| 1 | | /apps/files_sharing/api/v1/shares | GET |
| 2 | | /apps/files_sharing/api/v1/shares | PUT |
| 1 | | /apps/files_sharing/api/v1/shares | DELETE |
| 2 | | /apps/files_sharing/api/v1/shares | POST |
Scenario: CORS headers should be returned when setting CORS domain sending origin header in the Graph api
When user "Alice" lists all available spaces with headers using the Graph API
| header | value |
| Origin | https://aphno.badal |
Then the OCS status code should be "997"
And the HTTP status code should be "401"
Then the HTTP status code should be "200"
And the following headers should be set
| header | value |
| Access-Control-Allow-Headers | OC-Checksum,OC-Total-Length,OCS-APIREQUEST,X-OC-Mtime,OC-RequestAppPassword,Accept,Authorization,Brief,Content-Length,Content-Range,Content-Type,Date,Depth,Destination,Host,If,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Location,Lock-Token,Overwrite,Prefer,Range,Schedule-Reply,Timeout,User-Agent,X-Expected-Entity-Length,Accept-Language,Access-Control-Request-Method,Access-Control-Allow-Origin,Cache-Control,ETag,OC-Autorename,OC-CalDav-Import,OC-Chunked,OC-Etag,OC-FileId,OC-LazyOps,OC-Total-File-Length,Origin,X-Request-ID,X-Requested-With |
| Access-Control-Expose-Headers | Content-Location,DAV,ETag,Link,Lock-Token,OC-ETag,OC-Checksum,OC-FileId,OC-JobStatus-Location,OC-RequestAppPassword,Vary,Webdav-Location,X-Sabre-Status |
| Access-Control-Allow-Origin | https://aphno.badal |
| Access-Control-Allow-Methods | GET,OPTIONS,POST,PUT,DELETE,MKCOL,PROPFIND,PATCH,PROPPATCH,REPORT |
Examples:
| ocs_api_version | endpoint |
| 1 | /apps/files_sharing/api/v1/shares |
| 2 | /apps/files_sharing/api/v1/shares |
| header | value |
| Access-Control-Allow-Origin | https://aphno.badal |
@issue-8231
Scenario: CORS headers should be returned when setting CORS domain sending origin header in the Webdav api
When user "Alice" sends PROPFIND request to space "Alice Hansen" with headers using the WebDAV API
| header | value |
| Origin | https://aphno.badal |
Then the HTTP status code should be "207"
And the following headers should be set
| header | value |
| Access-Control-Allow-Origin | https://aphno.badal |

View File

@@ -1443,3 +1443,171 @@ Feature: get users
| user | errorToken |
| Alice-From-Wonderland | -From-Wonderland |
| Alice@From@Wonderland | @From@Wonderland |
@issue-7990
Scenario: non-admin user searches other users by e-mail
When user "Brian" searches for user "%22alice@example.org%22" using Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"required": [
"displayName",
"id",
"mail",
"userType"
],
"properties": {
"displayName": {
"type": "string",
"enum": ["Alice Hansen"]
},
"id": {
"type": "string",
"pattern": "^%user_id_pattern%$"
},
"mail": {
"type": "string",
"enum": ["alice@example.org"]
},
"userType": {
"type": "string",
"enum": ["Member"]
}
}
}
}
}
"""
Scenario: non-admin user searches for a disabled users
Given the user "Admin" has disabled user "Alice"
When user "Brian" searches for user "alice" using Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"required": [
"displayName",
"id",
"mail",
"userType"
],
"properties": {
"displayName": {
"type": "string",
"enum": ["Alice Hansen"]
},
"id": {
"type": "string",
"pattern": "^%user_id_pattern%$"
},
"mail": {
"type": "string",
"enum": ["alice@example.org"]
},
"userType": {
"type": "string",
"enum": ["Member"]
}
}
}
}
}
"""
Scenario: non-admin user searches for multiple users having same displayname
Given the user "Admin" has created a new user with the following attributes:
| userName | another-alice |
| displayName | Alice Hansen |
| email | another-alice@example.org |
| password | containsCharacters(*:!;_+-&) |
When user "Brian" searches for user "alice" using Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"items": [
{
"type": "object",
"required": [
"displayName",
"id",
"mail",
"userType"
],
"properties": {
"displayName": {
"type": "string",
"enum": ["Alice Hansen"]
},
"id": {
"type": "string",
"pattern": "^%user_id_pattern%$"
},
"mail": {
"type": "string",
"enum": ["alice@example.org"]
},
"userType": {
"type": "string",
"enum": ["Member"]
}
}
},
{
"type": "object",
"required": [
"displayName",
"id",
"mail",
"userType"
],
"properties": {
"displayName": {
"type": "string",
"enum": ["Alice Hansen"]
},
"id": {
"type": "string",
"pattern": "^%user_id_pattern%$"
},
"mail": {
"type": "string",
"enum": ["another-alice@example.org"]
},
"userType": {
"type": "string",
"enum": ["Member"]
}
}
}
]
}
}
}
"""

View File

@@ -27,13 +27,9 @@ Feature: Remove access to a drive item
| permissionsRole | resource-type | path |
| Viewer | file | textfile.txt |
| File Editor | file | textfile.txt |
| Co Owner | file | textfile.txt |
| Manager | file | textfile.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: user removes access to resource inside of a project space in the user share
@@ -56,13 +52,9 @@ Feature: Remove access to a drive item
| permissionsRole | resource-type | path |
| Viewer | file | textfile.txt |
| File Editor | file | textfile.txt |
| Co Owner | file | textfile.txt |
| Manager | file | textfile.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: user removes access to a resource in a group share
@@ -86,13 +78,9 @@ Feature: Remove access to a drive item
| permissionsRole | resource-type | path |
| Viewer | file | textfile.txt |
| File Editor | file | textfile.txt |
| Co Owner | file | textfile.txt |
| Manager | file | textfile.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: user removes access to a resource inside of a project space in group share
@@ -118,13 +106,9 @@ Feature: Remove access to a drive item
| permissionsRole | resource-type | path |
| Viewer | file | textfile.txt |
| File Editor | file | textfile.txt |
| Co Owner | file | textfile.txt |
| Manager | file | textfile.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: user removes access to a folder in link share

View File

@@ -0,0 +1,125 @@
Feature: Reshare a share invitation
As a user
I want to be able to reshare the share invitations to other users
So that they can have access to it
https://owncloud.dev/libre-graph-api/#/drives.permissions/Invite
Background:
Given these users have been created with default attributes and without skeleton files:
| username |
| Alice |
| Brian |
| Carol |
Scenario Outline: reshare a file to a user with different roles
Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt"
And user "Alice" has sent the following share invitation:
| resourceType | file |
| resource | textfile1.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
When user "Brian" sends the following share invitation using the Graph API:
| resourceType | file |
| resource | textfile1.txt |
| space | Shares |
| sharee | Carol |
| shareType | user |
| permissionsRole | <reshare-permissions-role> |
Then the HTTP status code should be "200"
And for user "Carol" the space Shares should contain these entries:
| textfile1.txt |
Examples:
| permissions-role | reshare-permissions-role |
| Viewer | Viewer |
| File Editor | Viewer |
| File Editor | File Editor |
Scenario Outline: reshare a folder to a user with different roles
Given user "Alice" has created folder "FolderToShare"
And user "Alice" has sent the following share invitation:
| resourceType | folder |
| resource | FolderToShare |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
When user "Brian" sends the following share invitation using the Graph API:
| resourceType | folder |
| resource | FolderToShare |
| space | Shares |
| sharee | Carol |
| shareType | user |
| permissionsRole | <reshare-permissions-role> |
Then the HTTP status code should be "200"
And for user "Carol" the space Shares should contain these entries:
| FolderToShare |
Examples:
| permissions-role | reshare-permissions-role |
| Viewer | Viewer |
| Editor | Viewer |
| Editor | Editor |
| Editor | Uploader |
Scenario Outline: reshare a file inside project space to a user with different roles
Given using spaces DAV path
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "NewSpace" with the default quota using the Graph API
And user "Alice" has uploaded a file inside space "NewSpace" with content "to share" to "textfile1.txt"
And user "Alice" has sent the following share invitation:
| resourceType | file |
| resource | textfile1.txt |
| space | NewSpace |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
When user "Brian" sends the following share invitation using the Graph API:
| resourceType | file |
| resource | textfile1.txt |
| space | Shares |
| sharee | Carol |
| shareType | user |
| permissionsRole | <reshare-permissions-role> |
Then the HTTP status code should be "200"
And for user "Carol" the space Shares should contain these entries:
| textfile1.txt |
Examples:
| permissions-role | reshare-permissions-role |
| Viewer | Viewer |
| File Editor | Viewer |
| File Editor | File Editor |
Scenario Outline: reshare a folder inside project space to a user with different roles
Given using spaces DAV path
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "NewSpace" with the default quota using the Graph API
And user "Alice" has created a folder "FolderToShare" in space "NewSpace"
And user "Alice" has sent the following share invitation:
| resourceType | folder |
| resource | FolderToShare |
| space | NewSpace |
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
When user "Brian" sends the following share invitation using the Graph API:
| resourceType | folder |
| resource | FolderToShare |
| space | Shares |
| sharee | Carol |
| shareType | user |
| permissionsRole | <reshare-permissions-role> |
Then the HTTP status code should be "200"
And for user "Carol" the space Shares should contain these entries:
| FolderToShare |
Examples:
| permissions-role | reshare-permissions-role |
| Viewer | Viewer |
| Editor | Viewer |
| Editor | Editor |
| Editor | Uploader |

View File

@@ -23,6 +23,8 @@ Feature: Send a sharing invitations
| shareType | user |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "200"
And for user "Brian" the space Shares should contain these entries:
| <path> |
And the JSON data of the response should match
"""
{
@@ -89,13 +91,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send share invitation to group with different roles
@@ -115,6 +113,10 @@ Feature: Send a sharing invitations
| shareType | group |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "200"
And for user "Brian" the space Shares should contain these entries:
| <path> |
And for user "Carol" the space Shares should contain these entries:
| <path> |
And the JSON data of the response should match
"""
{
@@ -181,13 +183,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send share invitation for a file to user with different permissions
@@ -265,7 +263,6 @@ Feature: Send a sharing invitations
Examples:
| permissionsAction |
| permissions/create |
| children/create |
| upload/create |
| path/read |
| quota/read |
@@ -275,11 +272,9 @@ Feature: Send a sharing invitations
| versions/read |
| deleted/read |
| basic/read |
| path/update |
| versions/update |
| deleted/update |
| permissions/update |
| standard/delete |
| permissions/delete |
| deleted/delete |
| permissions/deny |
@@ -461,7 +456,6 @@ Feature: Send a sharing invitations
Examples:
| permissionsAction |
| permissions/create |
| children/create |
| upload/create |
| path/read |
| quota/read |
@@ -471,11 +465,9 @@ Feature: Send a sharing invitations
| versions/read |
| deleted/read |
| basic/read |
| path/update |
| versions/update |
| deleted/update |
| permissions/update |
| standard/delete |
| permissions/delete |
| deleted/delete |
| permissions/deny |
@@ -667,13 +659,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send share invitation with expiration date to group with different roles
@@ -767,13 +755,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
@issue-7962
Scenario Outline: send share invitation to disabled user
@@ -854,13 +838,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send sharing invitation to a deleted group with different roles
@@ -917,13 +897,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send share invitation to deleted user
@@ -972,13 +948,9 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: try to send sharing invitation to multiple groups
@@ -1039,8 +1011,6 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
@@ -1104,10 +1074,121 @@ Feature: Send a sharing invitations
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Co Owner | file | /textfile1.txt |
| Manager | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Co Owner | folder | FolderToShare |
| Uploader | folder | FolderToShare |
| Manager | folder | FolderToShare |
Scenario Outline: send sharing invitation to non-existing group
Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt"
And user "Alice" has created folder "FolderToShare"
When user "Alice" sends the following share invitation using the Graph API:
| resourceType | <resource-type> |
| resource | <path> |
| space | Personal |
| sharee | nonExistentGroup |
| shareType | group |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string",
"enum": [
"generalException"
]
},
"message": {
"type": "string",
"enum": [
"itemNotFound: not found"
]
}
}
}
}
}
"""
Examples:
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Uploader | folder | FolderToShare |
Scenario Outline: send sharing invitation to already shared group
Given user "Carol" has been created with default attributes and without skeleton files
And group "grp1" has been created
And the following users have been added to the following groups
| username | groupname |
| Brian | grp1 |
| Carol | grp1 |
And user "Alice" has uploaded file with content "to share" to "/textfile1.txt"
And user "Alice" has created folder "FolderToShare"
And user "Alice" has sent the following share invitation:
| resourceType | <resource-type> |
| resource | <path> |
| space | Personal |
| sharee | grp1 |
| shareType | group |
| permissionsRole | <permissions-role> |
When user "Alice" sends the following share invitation using the Graph API:
| resourceType | <resource-type> |
| resource | <path> |
| space | Personal |
| sharee | grp1 |
| shareType | group |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "409"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string",
"enum": [
"nameAlreadyExists"
]
},
"message": {
"type": "string",
"pattern": "^error creating share: error: already exists:.*$"
}
}
}
}
}
"""
Examples:
| permissions-role | resource-type | path |
| Viewer | file | /textfile1.txt |
| File Editor | file | /textfile1.txt |
| Viewer | folder | FolderToShare |
| Editor | folder | FolderToShare |
| Uploader | folder | FolderToShare |

View File

@@ -205,8 +205,10 @@ Feature: Change data of space
"mimeType"
],
"properties": {
"type": "string",
"enum": ["text/markdown"]
"mimeType": {
"type": "string",
"enum": ["text/markdown"]
}
}
},
"id": {
@@ -298,8 +300,10 @@ Feature: Change data of space
"mimeType"
],
"properties": {
"type": "string",
"enum": ["<mimeType>"]
"mimeType": {
"type": "string",
"enum": ["<mimeType>"]
}
}
},
"id": {
@@ -393,8 +397,10 @@ Feature: Change data of space
"mimeType"
],
"properties": {
"type": "string",
"enum": ["text/markdown"]
"mimeType": {
"type": "string",
"enum": ["text/markdown"]
}
}
},
"id": {
@@ -479,8 +485,10 @@ Feature: Change data of space
"mimeType"
],
"properties": {
"type": "string",
"enum": ["image/png"]
"mimeType": {
"type": "string",
"enum": ["image/png"]
}
}
},
"id": {

View File

@@ -41,28 +41,28 @@ Feature: create space
"enum": ["Project Mars"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["project"]
},
"driveAlias": {
"type": "string",
"type": "string",
"enum": ["project/project-mars"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
},
"quota": {
"type": "object",
"required": [
"type": "object",
"required": [
"total"
],
"properties": {
"state": {
"type": "number",
"enum": [1000000000]
}
}
],
"properties": {
"total": {
"type": "number",
"enum": [1000000000]
}
}
},
"root": {
"type": "object",
@@ -70,11 +70,11 @@ Feature: create space
"webDavUrl"
],
"properties": {
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
},
"webUrl": {
"type": "string",
@@ -111,24 +111,24 @@ Feature: create space
"enum": ["Project Venus"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["project"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
},
"quota": {
"type": "object",
"required": [
"type": "object",
"required": [
"total"
],
"properties": {
"state": {
"type": "number",
"enum": [2000]
}
}
],
"properties": {
"total": {
"type": "number",
"enum": [2000]
}
}
},
"root": {
"type": "object",
@@ -136,11 +136,11 @@ Feature: create space
"webDavUrl"
],
"properties": {
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
},
"webUrl": {
"type": "string",

View File

@@ -167,11 +167,11 @@ Feature: List and create spaces
"enum": ["my project"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["project"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
}
}
@@ -227,13 +227,17 @@ Feature: List and create spaces
"user"
],
"properties": {
"type": "object",
"required": [
"id"
],
"properties": {
"type": "string",
"enum": ["%user_id%"]
"user": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"enum": ["%user_id%"]
}
}
}
}
},
@@ -343,6 +347,10 @@ Feature: List and create spaces
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
},
"webUrl": {
"type": "string",
"enum": ["%base_url%/f/%space_id%"]
}
}
}
@@ -454,28 +462,28 @@ Feature: List and create spaces
"enum": ["Shares"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["virtual"]
},
"driveAlias": {
"type": "string",
"type": "string",
"enum": ["virtual/shares"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
},
"quota": {
"type": "object",
"required": [
"type": "object",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": ["normal"]
}
}
],
"properties": {
"state": {
"type": "string",
"enum": ["normal"]
}
}
},
"root": {
"type": "object",
@@ -484,15 +492,15 @@ Feature: List and create spaces
"webDavUrl"
],
"properties": {
"eTag": {
"type": "string",
"enum": ["%space_etag%"]
},
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
"eTag": {
"type": "string",
"enum": ["%space_etag%"]
},
"webDavUrl": {
"type": "string",
"enum": ["%base_url%/dav/spaces/%space_id%"]
}
}
},
"webUrl": {
"type": "string",

View File

@@ -39,11 +39,11 @@ Feature: Space management
"enum": ["Project"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["project"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
}
}
@@ -70,11 +70,11 @@ Feature: Space management
"enum": ["Alice Hansen"]
},
"driveType": {
"type": "string",
"type": "string",
"enum": ["personal"]
},
"id": {
"type": "string",
"type": "string",
"enum": ["%space_id%"]
}
}

View File

@@ -39,3 +39,82 @@ Feature: upload resources using TUS protocol
Then for user "Alice" the space "Alice Hansen" should contain these entries:
| test.txt |
| upload.txt |
Scenario Outline: upload a zero-byte file inside a shared folder
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has created folder "testFolder"
And user "Alice" has shared folder "testFolder" with user "Brian" with permissions "all"
When user "Brian" uploads file "filesForUpload/zerobyte.txt" to "Shares/testFolder/textfile.txt" using the TUS protocol on the WebDAV API
Then the content of file "Shares/testFolder/textfile.txt" for user "Brian" should be ""
And the content of file "testFolder/textfile.txt" for user "Alice" should be ""
Examples:
| dav-path-version |
| old |
| new |
Scenario: upload a zero-byte file inside a shared folder (spaces dav path)
Given using spaces DAV path
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has created folder "testFolder"
And user "Alice" has shared folder "testFolder" with user "Brian" with permissions "all"
When user "Brian" uploads a file from "filesForUpload/zerobyte.txt" to "testFolder/textfile.txt" via TUS inside of the space "Shares" using the WebDAV API
Then for user "Brian" the content of the file "testFolder/textfile.txt" of the space "Shares" should be ""
And for user "Alice" the content of the file "testFolder/textfile.txt" of the space "Personal" should be ""
Scenario: upload a zero-byte file inside a project space
Given using spaces DAV path
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "new-space" with the default quota using the Graph API
When user "Alice" uploads a file from "filesForUpload/zerobyte.txt" to "textfile.txt" via TUS inside of the space "new-space" using the WebDAV API
Then for user "Alice" the content of the file "textfile.txt" of the space "new-space" should be ""
@issue-8003
Scenario Outline: replace a shared file with zero-byte file
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has uploaded file with content "This is TUS upload" to "textfile.txt"
And user "Alice" has shared file "textfile.txt" with user "Brian" with permissions "read,update"
When user "Brian" uploads file "filesForUpload/zerobyte.txt" to "Shares/textfile.txt" using the TUS protocol on the WebDAV API
Then the content of file "Shares/textfile.txt" for user "Brian" should be ""
And the content of file "textfile.txt" for user "Alice" should be ""
Examples:
| dav-path-version |
| old |
| new |
@issue-8003
Scenario: replace a shared file with zero-byte file (spaces dav path)
Given using spaces DAV path
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has uploaded file with content "This is TUS upload" to "textfile.txt"
And user "Alice" has shared file "textfile.txt" with user "Brian" with permissions "read,update"
When user "Brian" uploads a file from "filesForUpload/zerobyte.txt" to "textfile.txt" via TUS inside of the space "Shares" using the WebDAV API
Then for user "Brian" the content of the file "textfile.txt" of the space "Shares" should be ""
And for user "Alice" the content of the file "textfile.txt" of the space "Personal" should be ""
@issue-8003
Scenario: replace a file inside a project space with zero-byte file
Given using spaces DAV path
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has uploaded a file inside space "new-space" with content "This is TUS upload" to "textfile.txt"
When user "Alice" uploads a file from "filesForUpload/zerobyte.txt" to "textfile.txt" via TUS inside of the space "new-space" using the WebDAV API
Then for user "Alice" the content of the file "textfile.txt" of the space "new-space" should be ""
@issue-8003
Scenario: replace a file inside a shared project space with zero-byte file
Given using spaces DAV path
And user "Brian" has been created with default attributes and without skeleton files
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has uploaded a file inside space "new-space" with content "This is TUS upload" to "textfile.txt"
And user "Alice" has shared a space "new-space" with settings:
| shareWith | Brian |
| role | editor |
When user "Brian" uploads a file from "filesForUpload/zerobyte.txt" to "textfile.txt" via TUS inside of the space "new-space" using the WebDAV API
Then for user "Brian" the content of the file "textfile.txt" of the space "new-space" should be ""
And for user "Alice" the content of the file "textfile.txt" of the space "new-space" should be ""

View File

@@ -1937,122 +1937,6 @@ trait Provisioning {
);
}
/**
* @When /^the administrator changes the quota of user "([^"]*)" to "([^"]*)" using the provisioning API$/
*
* @param string $user
* @param string $quota
*
* @return void
*/
public function adminChangesTheQuotaOfUserUsingTheProvisioningApi(
string $user,
string $quota
):void {
$result = UserHelper::editUser(
$this->getBaseUrl(),
$this->getActualUsername($user),
'quota',
$quota,
$this->getAdminUsername(),
$this->getAdminPassword(),
$this->getStepLineRef(),
$this->ocsApiVersion
);
$this->response = $result;
}
/**
* @Given /^the administrator has (?:changed|set) the quota of user "([^"]*)" to "([^"]*)"$/
*
* @param string $user
* @param string $quota
*
* @return void
*/
public function adminHasChangedTheQuotaOfUserTo(
string $user,
string $quota
):void {
$user = $this->getActualUsername($user);
$this->adminChangesTheQuotaOfUserUsingTheProvisioningApi(
$user,
$quota
);
$this->theHTTPStatusCodeShouldBe(
200,
"could not change quota of user $user"
);
}
/**
* @param string $requestingUser
* @param string $targetUser
* @param string $quota
*
* @return ResponseInterface
*/
public function userChangeQuotaOfUserUsingProvisioningApi(
string $requestingUser,
string $targetUser,
string $quota
):ResponseInterface {
return UserHelper::editUser(
$this->getBaseUrl(),
$this->getActualUsername($targetUser),
'quota',
$quota,
$this->getActualUsername($requestingUser),
$this->getPasswordForUser($requestingUser),
$this->getStepLineRef(),
$this->ocsApiVersion
);
}
/**
* @When /^user "([^"]*)" changes the quota of user "([^"]*)" to "([^"]*)" using the provisioning API$/
*
* @param string $requestingUser
* @param string $targetUser
* @param string $quota
*
* @return void
*/
public function userChangesTheQuotaOfUserUsingTheProvisioningApi(
string $requestingUser,
string $targetUser,
string $quota
):void {
$this->response = $this->userChangeQuotaOfUserUsingProvisioningApi(
$requestingUser,
$targetUser,
$quota
);
$this->pushToLastStatusCodesArrays();
}
/**
* @Given /^user "([^"]*)" has changed the quota of user "([^"]*)" to "([^"]*)"$/
*
* @param string $requestingUser
* @param string $targetUser
* @param string $quota
*
* @return void
*/
public function userHasChangedTheQuotaOfUserUsingTheProvisioningApi(
string $requestingUser,
string $targetUser,
string $quota
):void {
$response = $this->userChangeQuotaOfUserUsingProvisioningApi(
$requestingUser,
$targetUser,
$quota
);
$this->theHTTPStatusCodeShouldBeBetween(200, 299, $response);
}
/**
* @param string $user
*
@@ -4450,68 +4334,6 @@ trait Provisioning {
}
}
/**
* @When the administrator sets the quota of user :user to :quota using the provisioning API
*
* @param string $user
* @param string $quota
*
* @return void
*/
public function adminSetsUserQuotaToUsingTheProvisioningApi(string $user, string $quota):void {
$user = $this->getActualUsername($user);
$body
= [
'key' => 'quota',
'value' => $quota,
];
$this->response = OcsApiHelper::sendRequest(
$this->getBaseUrl(),
$this->getAdminUsername(),
$this->getAdminPassword(),
"PUT",
"/cloud/users/$user",
$this->getStepLineRef(),
$body
);
}
/**
* @Given the quota of user :user has been set to :quota
*
* @param string $user
* @param string $quota
*
* @return void
*/
public function theQuotaOfUserHasBeenSetTo(string $user, string $quota):void {
$this->adminSetsUserQuotaToUsingTheProvisioningApi($user, $quota);
$this->theHTTPStatusCodeShouldBe(200);
}
/**
* @When the administrator gives unlimited quota to user :user using the provisioning API
*
* @param string $user
*
* @return void
*/
public function adminGivesUnlimitedQuotaToUserUsingTheProvisioningApi(string $user):void {
$this->adminSetsUserQuotaToUsingTheProvisioningApi($user, 'none');
}
/**
* @Given user :user has been given unlimited quota
*
* @param string $user
*
* @return void
*/
public function userHasBeenGivenUnlimitedQuota(string $user):void {
$this->theQuotaOfUserHasBeenSetTo($user, 'none');
}
/**
* @Then /^the user attributes returned by the API should include$/
*

View File

@@ -23,7 +23,9 @@ use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Psr\Http\Message\ResponseInterface;
use TestHelpers\GraphHelper;
use TestHelpers\WebDavHelper;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
require_once 'bootstrap.php';
@@ -139,9 +141,20 @@ class SharingNgContext implements Context {
$rows = $table->getRowsHash();
$spaceId = ($this->spacesContext->getSpaceByName($user, $rows['space']))["id"];
$itemId = ($rows['resourceType'] === 'folder')
? $this->spacesContext->getResourceId($user, $rows['space'], $rows['resource'])
: $this->spacesContext->getFileId($user, $rows['space'], $rows['resource']);
// for resharing a resource, "item-id" in API endpoint takes shareMountId
if ($rows['space'] === 'Shares') {
$itemId = GraphHelper::getShareMountId(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$user,
$this->featureContext->getPasswordForUser($user),
$rows['resource']
);
} else {
$itemId = ($rows['resourceType'] === 'folder')
? $this->spacesContext->getResourceId($user, $rows['space'], $rows['resource'])
: $this->spacesContext->getFileId($user, $rows['space'], $rows['resource']);
}
$sharees = array_map('trim', explode(',', $rows['sharee']));
$shareTypes = array_map('trim', explode(',', $rows['shareType']));
@@ -149,9 +162,10 @@ class SharingNgContext implements Context {
$shareeIds = [];
foreach ($sharees as $index => $sharee) {
$shareType = $shareTypes[$index];
$shareeIds[] = ($shareType === 'user')
// for non-exiting group or user, generate random id
$shareeIds[] = (($shareType === 'user')
? $this->featureContext->getAttributeOfCreatedUser($sharee, 'id')
: $this->featureContext->getAttributeOfCreatedGroup($sharee, 'id');
: $this->featureContext->getAttributeOfCreatedGroup($sharee, 'id')) ?: WebDavHelper::generateUUIDv4();
}
$permissionsRole = $rows['permissionsRole'] ?? null;
@@ -424,4 +438,34 @@ class SharingNgContext implements Context {
$this->removeSharePermission($sharer, 'link', $resourceType, $resource, $space)
);
}
/**
* @Then /^for user "([^"]*)" the space Shares should (not|)\s?contain these (files|entries):$/
*
* @param string $user
* @param string $shouldOrNot
* @param TableNode $table
*
* @return void
* @throws Exception
*/
public function forUserTheSpaceSharesShouldContainTheseEntries(string $user, string $shouldOrNot, TableNode $table): void {
$should = $shouldOrNot !== 'not';
$rows = $table->getRows();
$response = GraphHelper::getSharesSharedWithMe(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$user,
$this->featureContext->getPasswordForUser($user)
);
$contents = \json_decode($response->getBody()->getContents(), true);
$fileFound = empty(array_diff(array_map(fn ($row) => trim($row[0], '/'), $rows), array_column($contents['value'], 'name')));
$assertMessage = $should
? "Response does not contain the entry."
: "Response does contain the entry but should not.";
Assert::assertSame($should, $fileFound, $assertMessage);
}
}

View File

@@ -544,19 +544,22 @@ class SpacesContext implements Context {
/**
* @param string $user
* @param string $query
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public function listAllAvailableSpacesOfUser(string $user, string $query = ''): ResponseInterface {
public function listAllAvailableSpacesOfUser(string $user, string $query = '', array $headers = []): ResponseInterface {
$response = GraphHelper::getMySpaces(
$this->featureContext->getBaseUrl(),
$user,
$this->featureContext->getPasswordForUser($user),
"?" . $query,
$this->featureContext->getStepLineRef()
$this->featureContext->getStepLineRef(),
[],
$headers
);
$this->rememberTheAvailableSpaces($response);
return $response;
@@ -578,6 +581,28 @@ class SpacesContext implements Context {
$this->featureContext->setResponse($this->listAllAvailableSpacesOfUser($user, $query));
}
/**
* @When /^user "([^"]*)" lists all available spaces with headers using the Graph API$/
*
* @param string $user
* @param TableNode $headersTable
*
* @return void
*
* @throws GuzzleException
* @throws Exception
*/
public function theUserListsAllHisAvailableSpacesWithHeadersUsingTheGraphApi(string $user, TableNode $headersTable): void {
$this->featureContext->verifyTableNodeColumns(
$headersTable,
['header', 'value']
);
foreach ($headersTable as $row) {
$headers[$row['header']] = $row ['value'];
}
$this->featureContext->setResponse($this->listAllAvailableSpacesOfUser($user, '', $headers));
}
/**
* The method is used on the administration setting tab, which only the Admin user and the Space admin user have access to
*
@@ -1807,12 +1832,14 @@ class SpacesContext implements Context {
/**
* @When /^user "([^"]*)" copies (?:file|folder) "([^"]*)" from space "([^"]*)" to "([^"]*)" inside space "([^"]*)" using the WebDAV API$/
* @When /^user "([^"]*)" copies (?:file|folder) "([^"]*)" from space "([^"]*)" to "([^"]*)" inside space "([^"]*)"(?: with following headers) using the WebDAV API$/
*
* @param string $user
* @param string $fileSource
* @param string $fromSpaceName
* @param string $fileDestination
* @param string $toSpaceName
* @param TableNode|null $table
*
* @return void
* @throws GuzzleException
@@ -1822,10 +1849,22 @@ class SpacesContext implements Context {
string $fileSource,
string $fromSpaceName,
string $fileDestination,
string $toSpaceName
string $toSpaceName,
TableNode $table = null
):void {
$space = $this->getSpaceByName($user, $fromSpaceName);
$headers['Destination'] = $this->destinationHeaderValueWithSpaceName($user, $fileDestination, $toSpaceName);
if ($table !== null) {
$this->featureContext->verifyTableNodeColumns(
$table,
['header', 'value']
);
foreach ($table as $row) {
$headers[$row['header']] = $this->featureContext->substituteInLineCodes($row['value']);
}
}
$fullUrl = $space["root"]["webDavUrl"] . '/' . ltrim($fileSource, "/");
$this->featureContext->setResponse($this->copyFilesAndFoldersRequest($user, $fullUrl, $headers));
}
@@ -3514,17 +3553,44 @@ class SpacesContext implements Context {
);
}
/**
* @When /^user "([^"]*)" sends PROPFIND request to space "([^"]*)" with headers using the WebDAV API$/
*
* @param string $user
* @param string $spaceName
* @param TableNode $headersTable
*
* @return void
*
* @throws JsonException
*
* @throws GuzzleException
*/
public function userSendsPropfindRequestToSpaceWithHeaders(string $user, string $spaceName, $headersTable): void {
$this->featureContext->verifyTableNodeColumns(
$headersTable,
['header', 'value']
);
foreach ($headersTable as $row) {
$headers[$row['header']] = $row ['value'];
}
$this->featureContext->setResponse(
$this->sendPropfindRequestToSpace($user, $spaceName, '', $headers)
);
}
/**
* @param string $user
* @param string $spaceName
* @param string|null $resource
* @param array|null $headers
*
* @return ResponseInterface
* @throws GuzzleException
*
* @throws JsonException
*/
public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = ""): ResponseInterface {
public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = "", ?array $headers = []): ResponseInterface {
$this->setSpaceIDByName($user, $spaceName);
$properties = ['oc:permissions','oc:file-parent','oc:fileid','oc:share-types','oc:privatelink','d:resourcetype','oc:size','oc:name','d:getcontenttype','oc:tags','d:lockdiscovery','d:activelock'];
return WebDavHelper::propfind(
@@ -3536,7 +3602,9 @@ class SpacesContext implements Context {
$this->featureContext->getStepLineRef(),
"0",
"files",
WebDavHelper::DAV_VERSION_SPACES
WebDavHelper::DAV_VERSION_SPACES,
"",
$headers
);
}

View File

@@ -209,7 +209,8 @@ class TUSContext implements Context {
$client = new Client(
$this->featureContext->getBaseUrl(),
['verify' => false,
[
'verify' => false,
'headers' => $headers
]
);
@@ -230,6 +231,8 @@ class TUSContext implements Context {
if ($bytes !== null) {
$client->file($sourceFile, $destination)->createWithUpload($client->getKey(), $bytes);
} elseif (\filesize($sourceFile) === 0) {
$client->file($sourceFile, $destination)->createWithUpload($client->getKey(), 0);
} elseif ($noOfChunks === 1) {
$client->file($sourceFile, $destination)->upload();
} else {

View File

@@ -140,11 +140,11 @@ Feature: sharing
| old |
| new |
@smokeTest @skipOnGraph
@smokeTest
Scenario Outline: check quota of owners parent directory of a shared file
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
And the quota of user "Brian" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Brian Murphy" space to "0"
And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/myfile.txt"
And user "Alice" has shared file "myfile.txt" with user "Brian"
When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/myfile.txt" using the WebDAV API
@@ -162,17 +162,17 @@ Feature: sharing
| old |
| new |
@skipOnGraph
Scenario Outline: uploading to a user shared folder with read/write permission when the sharer has insufficient quota does not work
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and small skeleton files
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has created folder "FOLDER"
And user "Alice" has created a share with settings
| path | FOLDER |
| shareType | user |
| permissions | change |
| shareWith | Brian |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API
Then the HTTP status code should be "507"
And as "Alice" file "/FOLDER/myfile.txt" should not exist
@@ -193,7 +193,7 @@ Feature: sharing
| shareType | group |
| permissions | change |
| shareWith | grp1 |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API
Then the HTTP status code should be "507"
And as "Alice" file "/FOLDER/myfile.txt" should not exist
@@ -202,7 +202,7 @@ Feature: sharing
| old |
| new |
@skipOnGraph
Scenario Outline: uploading to a user shared folder with upload-only permission when the sharer has insufficient quota does not work
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
@@ -212,7 +212,7 @@ Feature: sharing
| shareType | user |
| permissions | create |
| shareWith | Brian |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API
Then the HTTP status code should be "507"
And as "Alice" file "/FOLDER/myfile.txt" should not exist
@@ -221,7 +221,7 @@ Feature: sharing
| old |
| new |
@skipOnGraph
Scenario Outline: uploading to a group shared folder with upload-only permission when the sharer has insufficient quota does not work
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
@@ -233,7 +233,7 @@ Feature: sharing
| shareType | group |
| permissions | create |
| shareWith | grp1 |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When user "Brian" uploads file "filesForUpload/textfile.txt" to "/Shares/FOLDER/myfile.txt" using the WebDAV API
Then the HTTP status code should be "507"
And as "Alice" file "/FOLDER/myfile.txt" should not exist

View File

@@ -1,4 +1,4 @@
@issue-1276 @issue-1277 @issue-1269
@issue-1276 @issue-1269
Feature: changing a public link share
As a user

View File

@@ -1,4 +1,4 @@
@issue-1276 @issue-1277
@issue-1276
Feature: upload to a public link share
As a user
@@ -93,17 +93,17 @@ Feature: upload to a public link share
| path | FOLDER |
| permissions | change |
| password | %public% |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When the public uploads file "test.txt" with password "%public%" and content "test2" using the new public WebDAV API
Then the HTTP status code should be "507"
@issue-1290
Scenario: uploading file to a public shared folder with upload-only permission when the sharer has insufficient quota does not work with public API
Given user "Alice" has created a public link share with settings
| path | FOLDER |
| permissions | create |
| password | %public% |
And the quota of user "Alice" has been set to "0"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "1"
When the public uploads file "test.txt" with password "%public%" and content "test2" using the new public WebDAV API
Then the HTTP status code should be "507"

View File

@@ -418,3 +418,31 @@ Feature: dav-versions
When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
Then the HTTP status code should be "403"
And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\Forbidden"
@issue-enterprise-6249
Scenario: upload empty content file and check versions after multiple restores
Given user "Alice" has uploaded file with content "" to "textfile.txt"
And user "Alice" has uploaded file with content "test content" to "textfile.txt"
And the version folder of file "textfile.txt" for user "Alice" should contain "1" element
When user "Alice" restores version index "1" of file "textfile.txt" using the WebDAV API
Then the HTTP status code should be "204"
And the content of file "textfile.txt" for user "Alice" should be ""
And the version folder of file "textfile.txt" for user "Alice" should contain "1" elements
When user "Alice" restores version index "1" of file "textfile.txt" using the WebDAV API
Then the HTTP status code should be "204"
And the content of file "textfile.txt" for user "Alice" should be "test content"
And the version folder of file "textfile.txt" for user "Alice" should contain "1" elements
Scenario: update with empty content and check versions after multiple restores
Given user "Alice" has uploaded file with content "test content" to "textfile.txt"
And user "Alice" has uploaded file with content "" to "textfile.txt"
And the version folder of file "textfile.txt" for user "Alice" should contain "1" element
When user "Alice" restores version index "1" of file "textfile.txt" using the WebDAV API
Then the HTTP status code should be "204"
And the content of file "textfile.txt" for user "Alice" should be "test content"
And the version folder of file "textfile.txt" for user "Alice" should contain "1" elements
When user "Alice" restores version index "1" of file "textfile.txt" using the WebDAV API
Then the HTTP status code should be "204"
And the content of file "textfile.txt" for user "Alice" should be ""
And the version folder of file "textfile.txt" for user "Alice" should contain "1" elements

View File

@@ -1,4 +1,4 @@
@issue-1313 @skipOnGraph
@issue-1313 @skipOnReva
Feature: get quota
As a user
I want to be able to find out my available storage quota
@@ -11,9 +11,9 @@ Feature: get quota
Scenario Outline: retrieving folder quota when no quota is set
Given using <dav-path-version> DAV path
When the administrator gives unlimited quota to user "Alice" using the provisioning API
When user "Admin" changes the quota of the "Alice Hansen" space to "0"
Then the HTTP status code should be "200"
And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "-3"
And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "0"
Examples:
| dav-path-version |
| old |
@@ -23,66 +23,67 @@ Feature: get quota
@smokeTest
Scenario Outline: retrieving folder quota when quota is set
Given using <dav-path-version> DAV path
When the administrator sets the quota of user "Alice" to "10 MB" using the provisioning API
When user "Admin" changes the quota of the "Alice Hansen" space to "10000"
Then the HTTP status code should be "200"
And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "10485406"
And as user "Alice" folder "/" should contain a property "d:quota-available-bytes" with value "10000"
Examples:
| dav-path-version |
| old |
| new |
| spaces |
@issue-8197
Scenario Outline: retrieving folder quota of shared folder with quota when no quota is set for recipient
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
And user "Alice" has been given unlimited quota
And the quota of user "Brian" has been set to "10 MB"
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "0"
And user "Admin" has changed the quota of the personal space of "Brian Murphy" space to "10000"
And user "Brian" has created folder "/testquota"
And user "Brian" has uploaded file "/testquota/Brian.txt" of size 1000 bytes
And user "Brian" has created a share with settings
| path | testquota |
| shareType | user |
| permissions | all |
| shareWith | Alice |
When user "Alice" gets the following properties of folder "/testquota" using the WebDAV API
When user "Alice" gets the following properties of folder "<folder-path>" inside space "Shares" using the WebDAV API
| propertyName |
| d:quota-available-bytes |
Then the HTTP status code should be "200"
And the single response should contain a property "d:quota-available-bytes" with value "10485406"
Then the HTTP status code should be "207"
And the single response should contain a property "d:quota-available-bytes" with value "9000"
Examples:
| dav-path-version |
| old |
| new |
| spaces |
| dav-path-version | folder-path |
| old | /Shares/testquota |
| new | /Shares/testquota |
| spaces | /testquota |
@issue-8197
Scenario Outline: retrieving folder quota when quota is set and a file was uploaded
Given using <dav-path-version> DAV path
And the quota of user "Alice" has been set to "1 KB"
And user "Alice" has uploaded file "/prueba.txt" of size 93 bytes
And user "Admin" has changed the quota of the personal space of "Alice Hansen" space to "10000"
And user "Alice" has uploaded file "/prueba.txt" of size 1000 bytes
When user "Alice" gets the following properties of folder "/" using the WebDAV API
| propertyName |
| d:quota-available-bytes |
Then the HTTP status code should be "201"
And the single response should contain a property "d:quota-available-bytes" with value "577"
Then the HTTP status code should be "207"
And the single response should contain a property "d:quota-available-bytes" with value "9000"
Examples:
| dav-path-version |
| old |
| new |
| spaces |
@skipOnReva
Scenario Outline: retrieving folder quota when quota is set and a file was received
Given using <dav-path-version> DAV path
And user "Brian" has been created with default attributes and without skeleton files
And the quota of user "Brian" has been set to "1 KB"
And user "Admin" has changed the quota of the personal space of "Brian Murphy" space to "10000"
And user "Alice" has uploaded file "/Alice.txt" of size 93 bytes
And user "Alice" has shared file "Alice.txt" with user "Brian"
When user "Brian" gets the following properties of folder "/" using the WebDAV API
| propertyName |
| d:quota-available-bytes |
Then the HTTP status code should be "200"
And the single response should contain a property "d:quota-available-bytes" with value "670"
Then the HTTP status code should be "207"
And the single response should contain a property "d:quota-available-bytes" with value "10000"
Examples:
| dav-path-version |
| old |

View File

@@ -208,3 +208,39 @@ Feature: upload file
| spaces | "filewithLF-and-CR\r\n" | ZmlsZXdpdGhMRi1hbmQtQ1INCgo= |
| spaces | "folder/file" | Zm9sZGVyL2ZpbGU= |
| spaces | "my\\file" | bXkMaWxl |
Scenario Outline: upload a zero-byte file
Given using <dav-path-version> DAV path
When user "Alice" uploads file "filesForUpload/zerobyte.txt" to "textfile.txt" using the TUS protocol on the WebDAV API
Then the content of file "textfile.txt" for user "Alice" should be ""
Examples:
| dav-path-version |
| old |
| new |
| spaces |
@issue-8003
Scenario Outline: replace a file with zero-byte file
Given using <dav-path-version> DAV path
And user "Alice" has uploaded file with content "This is TUS upload" to "textfile.txt"
When user "Alice" uploads file "filesForUpload/zerobyte.txt" to "textfile.txt" using the TUS protocol on the WebDAV API
Then the content of file "textfile.txt" for user "Alice" should be ""
Examples:
| dav-path-version |
| old |
| new |
| spaces |
@issue-8003
Scenario Outline: replace a file inside a folder with zero-byte file
Given using <dav-path-version> DAV path
And user "Alice" has created folder "testFolder"
And user "Alice" has uploaded file with content "This is TUS upload" to "testFolder/textfile.txt"
When user "Alice" uploads file "filesForUpload/zerobyte.txt" to "testFolder/textfile.txt" using the TUS protocol on the WebDAV API
Then the content of file "testFolder/textfile.txt" for user "Alice" should be ""
Examples:
| dav-path-version |
| old |
| new |
| spaces |

View File

@@ -25,6 +25,7 @@ import (
"strings"
"github.com/cs3org/reva/v2/pkg/storagespace"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
@@ -320,7 +321,7 @@ func (s *service) GetPath(ctx context.Context, req *provider.GetPathRequest) (*p
return &provider.GetPathResponse{
Status: status.NewOK(ctx),
Path: receivedShare.MountPoint.Path,
Path: filepath.Clean("/" + receivedShare.MountPoint.Path),
}, nil
}
@@ -397,7 +398,7 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora
var shareInfo map[string]*provider.ResourceInfo
var err error
if fetchShares {
receivedShares, shareInfo, err = s.fetchShares(ctx)
receivedShares, shareInfo, err = s.fetchShares(ctx, req.Opaque, []string{}, &fieldmaskpb.FieldMask{ /*TODO mtime and etag only?*/ })
if err != nil {
return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest")
}
@@ -708,7 +709,7 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide
if !ok {
return nil, fmt.Errorf("missing user in context")
}
receivedShares, shareMd, err := s.fetchShares(ctx)
receivedShares, shareMd, err := s.fetchShares(ctx, req.Opaque, req.ArbitraryMetadataKeys, req.FieldMask)
if err != nil {
return nil, err
}
@@ -804,7 +805,7 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer
// The root is empty, it is filled by mountpoints
// so, when accessing the root via /dav/spaces, we need to list the accepted shares with their mountpoint
receivedShares, _, err := s.fetchShares(ctx)
receivedShares, shareMd, err := s.fetchShares(ctx, req.Opaque, req.ArbitraryMetadataKeys, req.FieldMask)
if err != nil {
return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest")
}
@@ -820,37 +821,45 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer
continue
}
statRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{
Opaque: req.Opaque,
Ref: &provider.Reference{
ResourceId: share.Share.ResourceId,
Path: ".",
},
ArbitraryMetadataKeys: req.ArbitraryMetadataKeys,
})
switch {
case err != nil:
appctx.GetLogger(ctx).Error().
Err(err).
Interface("share", share).
Msg("sharesstorageprovider: could not make stat request when listing virtual root, skipping")
continue
case statRes.Status.Code != rpc.Code_CODE_OK:
appctx.GetLogger(ctx).Debug().
Interface("share", share).
Interface("status", statRes.Status).
Msg("sharesstorageprovider: could not stat share when listing virtual root, skipping")
continue
info := shareMd[share.GetShare().GetId().GetOpaqueId()]
if info == nil {
if share.GetShare().GetResourceId().GetSpaceId() == "" {
// convert backwards compatible share id
share.Share.ResourceId.StorageId, share.Share.ResourceId.SpaceId = storagespace.SplitStorageID(share.GetShare().GetResourceId().GetSpaceId())
}
statRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{
Opaque: req.Opaque,
Ref: &provider.Reference{
ResourceId: share.Share.ResourceId,
Path: ".",
},
ArbitraryMetadataKeys: req.ArbitraryMetadataKeys,
})
switch {
case err != nil:
appctx.GetLogger(ctx).Error().
Err(err).
Interface("share", share).
Msg("sharesstorageprovider: could not make stat request when listing virtual root, skipping")
continue
case statRes.Status.Code != rpc.Code_CODE_OK:
appctx.GetLogger(ctx).Debug().
Interface("share", share).
Interface("status", statRes.Status).
Msg("sharesstorageprovider: could not stat share when listing virtual root, skipping")
continue
}
info = statRes.Info
}
// override info
info := statRes.Info
// override resource id info
info.Id = &provider.ResourceId{
StorageId: utils.ShareStorageProviderID,
SpaceId: utils.ShareStorageSpaceID,
OpaqueId: share.Share.Id.OpaqueId,
}
info.Path = filepath.Base(share.MountPoint.Path)
info.Name = info.Path
infos = append(infos, info)
}
@@ -1067,7 +1076,13 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere
// look up share for this resourceid
lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{
Filters: []*collaboration.Filter{
// FIXME filter by accepted ... and by mountpoint?
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_ACCEPTED,
},
},
// TODO filter by mountpoint?
},
})
if err != nil {
@@ -1077,6 +1092,7 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere
return nil, lsRes.Status, nil
}
for _, receivedShare := range lsRes.Shares {
// make sure to skip unaccepted shares
if receivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
continue
}
@@ -1121,7 +1137,7 @@ func (s *service) rejectReceivedShare(ctx context.Context, receivedShare *collab
return errtypes.NewErrtypeFromStatus(res.Status)
}
func (s *service) fetchShares(ctx context.Context) ([]*collaboration.ReceivedShare, map[string]*provider.ResourceInfo, error) {
func (s *service) fetchShares(ctx context.Context, opaque *typesv1beta1.Opaque, arbitraryMetadataKeys []string, fieldMask *field_mask.FieldMask) ([]*collaboration.ReceivedShare, map[string]*provider.ResourceInfo, error) {
sharingCollaborationClient, err := s.sharingCollaborationSelector.Next()
if err != nil {
return nil, nil, err
@@ -1152,7 +1168,12 @@ func (s *service) fetchShares(ctx context.Context) ([]*collaboration.ReceivedSha
// convert backwards compatible share id
rs.Share.ResourceId.StorageId, rs.Share.ResourceId.SpaceId = storagespace.SplitStorageID(rs.Share.ResourceId.StorageId)
}
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: rs.Share.ResourceId}})
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{
Opaque: opaque,
Ref: &provider.Reference{ResourceId: rs.Share.ResourceId},
ArbitraryMetadataKeys: arbitraryMetadataKeys,
FieldMask: fieldMask,
})
if err != nil {
appctx.GetLogger(ctx).Error().
Err(err).

View File

@@ -702,13 +702,27 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro
}
}
md, err := s.storage.GetMD(ctx, req.Ref, []string{}, []string{"id"})
md, err := s.storage.GetMD(ctx, req.Ref, []string{}, []string{"id", "status"})
if err != nil {
return &provider.DeleteResponse{
Status: status.NewStatusFromErrType(ctx, "can't stat resource to delete", err),
}, nil
}
if utils.ReadPlainFromOpaque(md.GetOpaque(), "status") == "processing" {
return &provider.DeleteResponse{
Status: &rpc.Status{
Code: rpc.Code_CODE_UNAVAILABLE,
Message: "file is processing",
},
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"status": {Decoder: "plain", Value: []byte("processing")},
},
},
}, nil
}
err = s.storage.Delete(ctx, req.Ref)
return &provider.DeleteResponse{

View File

@@ -198,7 +198,7 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
Status: status.NewPermissionDenied(ctx, nil, "no permission to add grants on shared resource"),
}, err
}
// check if the requested share creation has sufficient permissions to do so.
// check if the share creator has sufficient permissions to do so.
if shareCreationAllowed := conversions.SufficientCS3Permissions(
sRes.GetInfo().GetPermissionSet(),
req.GetGrant().GetPermissions().GetPermissions(),
@@ -207,6 +207,14 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
}, nil
}
// check if the requested permission are plausible for the Resource
if sRes.GetInfo().GetType() == provider.ResourceType_RESOURCE_TYPE_FILE {
if newPermissions := req.GetGrant().GetPermissions().GetPermissions(); newPermissions.GetCreateContainer() || newPermissions.GetMove() || newPermissions.GetDelete() {
return &collaboration.CreateShareResponse{
Status: status.NewInvalid(ctx, "cannot set the requested permissions on that type of resource"),
}, nil
}
}
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
return &collaboration.CreateShareResponse{

View File

@@ -303,6 +303,7 @@ func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gate
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {
@@ -521,6 +522,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {

View File

@@ -286,8 +286,6 @@ func (s *svc) ApplyLayout(ctx context.Context, ns string, useLoggedInUserNS bool
func addAccessHeaders(w http.ResponseWriter, r *http.Request) {
headers := w.Header()
// the webdav api is accessible from anywhere
headers.Set("Access-Control-Allow-Origin", "*")
// all resources served via the DAV endpoint should have the strictest possible as default
headers.Set("Content-Security-Policy", "default-src 'none';")
// disable sniffing the content type for IE

View File

@@ -486,12 +486,16 @@ func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r
w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
if sendTusHeaders {
w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderTusVersion, net.HeaderTusExtension}, ", "))
w.Header().Add(net.HeaderAccessControlExposeHeaders, net.HeaderTusResumable)
w.Header().Add(net.HeaderAccessControlExposeHeaders, net.HeaderTusVersion)
w.Header().Add(net.HeaderAccessControlExposeHeaders, net.HeaderTusExtension)
w.Header().Set(net.HeaderAccessControlExposeHeaders, strings.Join(w.Header().Values(net.HeaderAccessControlExposeHeaders), ", "))
w.Header().Set(net.HeaderTusResumable, "1.0.0")
w.Header().Set(net.HeaderTusVersion, "1.0.0")
w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration")
w.Header().Set(net.HeaderTusExtension, "creation, creation-with-upload, checksum, expiration")
}
w.Header().Set(net.HeaderVary, net.HeaderPrefer)
w.Header().Add(net.HeaderVary, net.HeaderPrefer)
w.Header().Set(net.HeaderVary, strings.Join(w.Header().Values(net.HeaderVary), ", "))
if returnMinimal {
w.Header().Set(net.HeaderPreferenceApplied, "return=minimal")
}

View File

@@ -292,58 +292,59 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ
}
// ony send actual PUT request if file has bytes. Otherwise the initiate file upload request creates the file
// if length != 0 { // FIXME bring back 0 byte file upload handling, see https://github.com/owncloud/ocis/issues/2609
var ep, token string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
ep, token = p.UploadEndpoint, p.Token
if length != 0 {
var ep, token string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
ep, token = p.UploadEndpoint, p.Token
}
}
}
httpReq, err := rhttp.NewRequest(ctx, http.MethodPut, ep, r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
Propagator.Inject(ctx, propagation.HeaderCarrier(httpReq.Header))
httpReq.Header.Set(datagateway.TokenTransportHeader, token)
httpRes, err := s.client.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing PUT request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
if httpRes.StatusCode != http.StatusOK {
if httpRes.StatusCode == http.StatusPartialContent {
w.WriteHeader(http.StatusPartialContent)
httpReq, err := rhttp.NewRequest(ctx, http.MethodPut, ep, r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if httpRes.StatusCode == errtypes.StatusChecksumMismatch {
w.WriteHeader(http.StatusBadRequest)
b, err := errors.Marshal(http.StatusBadRequest, "The computed checksum does not match the one received from the client.", "")
errors.HandleWebdavError(&log, w, b, err)
Propagator.Inject(ctx, propagation.HeaderCarrier(httpReq.Header))
httpReq.Header.Set(datagateway.TokenTransportHeader, token)
httpReq.ContentLength = length
httpRes, err := s.client.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing PUT request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
if httpRes.StatusCode != http.StatusOK {
if httpRes.StatusCode == http.StatusPartialContent {
w.WriteHeader(http.StatusPartialContent)
return
}
if httpRes.StatusCode == errtypes.StatusChecksumMismatch {
w.WriteHeader(http.StatusBadRequest)
b, err := errors.Marshal(http.StatusBadRequest, "The computed checksum does not match the one received from the client.", "")
errors.HandleWebdavError(&log, w, b, err)
return
}
log.Error().Err(err).Msg("PUT request to data server failed")
w.WriteHeader(httpRes.StatusCode)
return
}
log.Error().Err(err).Msg("PUT request to data server failed")
w.WriteHeader(httpRes.StatusCode)
return
}
// copy headers if they are present
if httpRes.Header.Get(net.HeaderETag) != "" {
w.Header().Set(net.HeaderETag, httpRes.Header.Get(net.HeaderETag))
}
if httpRes.Header.Get(net.HeaderOCETag) != "" {
w.Header().Set(net.HeaderOCETag, httpRes.Header.Get(net.HeaderOCETag))
}
if httpRes.Header.Get(net.HeaderOCFileID) != "" {
w.Header().Set(net.HeaderOCFileID, httpRes.Header.Get(net.HeaderOCFileID))
}
if httpRes.Header.Get(net.HeaderLastModified) != "" {
w.Header().Set(net.HeaderLastModified, httpRes.Header.Get(net.HeaderLastModified))
// copy headers if they are present
if httpRes.Header.Get(net.HeaderETag) != "" {
w.Header().Set(net.HeaderETag, httpRes.Header.Get(net.HeaderETag))
}
if httpRes.Header.Get(net.HeaderOCETag) != "" {
w.Header().Set(net.HeaderOCETag, httpRes.Header.Get(net.HeaderOCETag))
}
if httpRes.Header.Get(net.HeaderOCFileID) != "" {
w.Header().Set(net.HeaderOCFileID, httpRes.Header.Get(net.HeaderOCFileID))
}
if httpRes.Header.Get(net.HeaderLastModified) != "" {
w.Header().Set(net.HeaderLastModified, httpRes.Header.Get(net.HeaderLastModified))
}
}
// file was new

View File

@@ -252,56 +252,58 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.
// for creation-with-upload extension forward bytes to dataprovider
// TODO check this really streams
if r.Header.Get(net.HeaderContentType) == "application/offset+octet-stream" {
length, err := strconv.ParseInt(r.Header.Get(net.HeaderContentLength), 10, 64)
if err != nil {
log.Debug().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusBadRequest)
return
}
var httpRes *http.Response
finishUpload := true
if uploadLength > 0 {
var httpRes *http.Response
httpReq, err := rhttp.NewRequest(ctx, http.MethodPatch, ep, r.Body)
if err != nil {
log.Debug().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
Propagator.Inject(ctx, propagation.HeaderCarrier(httpReq.Header))
httpReq, err := rhttp.NewRequest(ctx, http.MethodPatch, ep, r.Body)
if err != nil {
log.Debug().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
Propagator.Inject(ctx, propagation.HeaderCarrier(httpReq.Header))
httpReq.Header.Set(net.HeaderContentType, r.Header.Get(net.HeaderContentType))
httpReq.Header.Set(net.HeaderContentLength, r.Header.Get(net.HeaderContentLength))
if r.Header.Get(net.HeaderUploadOffset) != "" {
httpReq.Header.Set(net.HeaderUploadOffset, r.Header.Get(net.HeaderUploadOffset))
} else {
httpReq.Header.Set(net.HeaderUploadOffset, "0")
}
httpReq.Header.Set(net.HeaderTusResumable, r.Header.Get(net.HeaderTusResumable))
httpReq.Header.Set(net.HeaderContentType, r.Header.Get(net.HeaderContentType))
httpReq.Header.Set(net.HeaderContentLength, r.Header.Get(net.HeaderContentLength))
if r.Header.Get(net.HeaderUploadOffset) != "" {
httpReq.Header.Set(net.HeaderUploadOffset, r.Header.Get(net.HeaderUploadOffset))
} else {
httpReq.Header.Set(net.HeaderUploadOffset, "0")
}
httpReq.Header.Set(net.HeaderTusResumable, r.Header.Get(net.HeaderTusResumable))
httpRes, err = s.client.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing PATCH request to data gateway")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
httpRes, err = s.client.Do(httpReq)
if err != nil || httpRes == nil {
log.Error().Err(err).Msg("error doing PATCH request to data gateway")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
w.Header().Set(net.HeaderUploadOffset, httpRes.Header.Get(net.HeaderUploadOffset))
w.Header().Set(net.HeaderTusResumable, httpRes.Header.Get(net.HeaderTusResumable))
w.Header().Set(net.HeaderTusUploadExpires, httpRes.Header.Get(net.HeaderTusUploadExpires))
if httpRes.StatusCode != http.StatusNoContent {
w.WriteHeader(httpRes.StatusCode)
return
}
if httpRes.StatusCode != http.StatusNoContent {
w.WriteHeader(httpRes.StatusCode)
return
}
// check if upload was fully completed
if length == 0 || httpRes.Header.Get(net.HeaderUploadOffset) == r.Header.Get(net.HeaderUploadLength) {
// get uploaded file metadata
w.Header().Set(net.HeaderUploadOffset, httpRes.Header.Get(net.HeaderUploadOffset))
w.Header().Set(net.HeaderTusResumable, httpRes.Header.Get(net.HeaderTusResumable))
w.Header().Set(net.HeaderTusUploadExpires, httpRes.Header.Get(net.HeaderTusUploadExpires))
if httpRes.Header.Get(net.HeaderOCMtime) != "" {
w.Header().Set(net.HeaderOCMtime, httpRes.Header.Get(net.HeaderOCMtime))
}
if resid, err := storagespace.ParseID(httpRes.Header.Get(net.HeaderOCFileID)); err == nil {
sReq.Ref = &provider.Reference{
ResourceId: &resid,
}
}
finishUpload = httpRes.Header.Get(net.HeaderUploadOffset) == r.Header.Get(net.HeaderUploadLength)
}
// check if upload was fully completed
if uploadLength == 0 || finishUpload {
// get uploaded file metadata
sRes, err := client.Stat(ctx, sReq)
if err != nil {
@@ -311,7 +313,6 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.
}
if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND {
if sRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED {
// the token expired during upload, so the stat failed
// and we can't do anything about it.
@@ -330,10 +331,6 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.
w.WriteHeader(http.StatusInternalServerError)
return
}
if httpRes != nil && httpRes.Header != nil && httpRes.Header.Get(net.HeaderOCMtime) != "" {
// set the "accepted" value if returned in the upload response headers
w.Header().Set(net.HeaderOCMtime, httpRes.Header.Get(net.HeaderOCMtime))
}
// get WebDav permissions for file
isPublic := false

View File

@@ -36,6 +36,7 @@ import (
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/response"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/conversions"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
@@ -63,62 +64,110 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) {
return
}
rs, ocsResponse := getReceivedShareFromID(ctx, client, shareID)
receivedShare, ocsResponse := getReceivedShareFromID(ctx, client, shareID)
if ocsResponse != nil {
response.WriteOCSResponse(w, r, *ocsResponse, nil)
return
}
sharedResource, ocsResponse := getSharedResource(ctx, client, rs.Share.Share.ResourceId)
sharedResource, ocsResponse := getSharedResource(ctx, client, receivedShare.Share.ResourceId)
if ocsResponse != nil {
response.WriteOCSResponse(w, r, *ocsResponse, nil)
return
}
lrs, ocsResponse := getSharesList(ctx, client)
if ocsResponse != nil {
response.WriteOCSResponse(w, r, *ocsResponse, nil)
mount, unmountedShares, err := GetMountpointAndUnmountedShares(ctx, client, sharedResource.Info)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not determine mountpoint", err)
return
}
// first update the requested share
receivedShare.State = collaboration.ShareState_SHARE_STATE_ACCEPTED
// we need to add a path to the share
receivedShare.MountPoint = &provider.Reference{
Path: mount,
}
updateMask := &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}}
data, meta, err := h.updateReceivedShare(r.Context(), receivedShare, updateMask)
if err != nil {
// we log an error for affected shares, for the actual share we return an error
response.WriteOCSData(w, r, meta, data, err)
return
}
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
// then update other unmounted shares to the same resource
for _, rs := range unmountedShares {
if rs.GetShare().GetId().GetOpaqueId() == shareID {
// we already updated this one
continue
}
rs.State = collaboration.ShareState_SHARE_STATE_ACCEPTED
// set the same mountpoint as for the requested received share
rs.MountPoint = &provider.Reference{
Path: mount,
}
_, _, err := h.updateReceivedShare(r.Context(), rs, updateMask)
if err != nil {
// we log an error for affected shares, the actual share was successful
appctx.GetLogger(ctx).Error().Err(err).Str("received_share", shareID).Str("affected_share", rs.GetShare().GetId().GetOpaqueId()).Msg("could not update affected received share")
}
}
}
// GetMountpointAndUnmountedShares returns a new or existing mountpoint for the given info and produces a list of unmounted received shares for the same resource
func GetMountpointAndUnmountedShares(ctx context.Context, gwc gateway.GatewayAPIClient, info *provider.ResourceInfo) (string, []*collaboration.ReceivedShare, error) {
unmountedShares := []*collaboration.ReceivedShare{}
receivedShares, err := listReceivedShares(ctx, gwc)
if err != nil {
return "", unmountedShares, err
}
// we need to sort the received shares by mount point in order to make things easier to evaluate.
base := path.Base(sharedResource.GetInfo().GetPath())
mount := base
var mountedShares []*collaboration.ReceivedShare
sharesToAccept := map[string]bool{shareID: true}
for _, s := range lrs.Shares {
if utils.ResourceIDEqual(s.Share.ResourceId, rs.Share.Share.GetResourceId()) {
mount := filepath.Clean(info.Name)
existingMountpoint := ""
mountedShares := make([]*collaboration.ReceivedShare, 0, len(receivedShares))
for _, s := range receivedShares {
if utils.ResourceIDEqual(s.Share.ResourceId, info.GetId()) {
if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
mount = s.MountPoint.Path
// a share to the resource already exists and is mounted, remember the mount point
_, err := utils.GetResourceByID(ctx, s.Share.ResourceId, gwc)
if err == nil {
existingMountpoint = s.MountPoint.Path
}
} else {
sharesToAccept[s.Share.Id.OpaqueId] = true
}
} else {
if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
s.Hidden = h.getReceivedShareHideFlagFromShareID(r.Context(), shareID)
mountedShares = append(mountedShares, s)
// a share to the resource already exists but is not mounted, collect the unmounted share
unmountedShares = append(unmountedShares, s)
}
}
if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
mountedShares = append(mountedShares, s)
}
}
compareMountPoint := func(i, j int) bool {
sort.Slice(mountedShares, func(i, j int) bool {
return mountedShares[i].MountPoint.Path > mountedShares[j].MountPoint.Path
}
sort.Slice(mountedShares, compareMountPoint)
})
// now we have a list of shares, we want to iterate over all of them and check for name collisions
if existingMountpoint != "" {
// we want to reuse the same mountpoint for all unmounted shares to the same resource
return existingMountpoint, unmountedShares, nil
}
// we have a list of shares, we want to iterate over all of them and check for name collisions
for i, ms := range mountedShares {
if ms.MountPoint.Path == mount {
// does the shared resource still exist?
res, err := client.Stat(ctx, &provider.StatRequest{
Ref: &provider.Reference{
ResourceId: ms.Share.ResourceId,
},
})
if err == nil && res.Status.Code == rpc.Code_CODE_OK {
_, err := utils.GetResourceByID(ctx, ms.Share.ResourceId, gwc)
if err == nil {
// The mount point really already exists, we need to insert a number into the filename
ext := filepath.Ext(base)
name := strings.TrimSuffix(base, ext)
ext := filepath.Ext(mount)
name := strings.TrimSuffix(mount, ext)
// be smart about .tar.(gz|bz) files
if strings.HasSuffix(name, ".tar") {
name = strings.TrimSuffix(name, ".tar")
@@ -130,26 +179,7 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) {
// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
}
}
// we need to add a path to the share
receivedShare := &collaboration.ReceivedShare{
Share: &collaboration.Share{
Id: &collaboration.ShareId{OpaqueId: shareID},
},
State: collaboration.ShareState_SHARE_STATE_ACCEPTED,
Hidden: h.getReceivedShareHideFlagFromShareID(r.Context(), shareID),
MountPoint: &provider.Reference{
Path: mount,
},
}
updateMask := &fieldmaskpb.FieldMask{Paths: []string{"state", "hidden", "mount_point"}}
for id := range sharesToAccept {
data := h.updateReceivedShare(w, r, receivedShare, updateMask)
// only render the data for the changed share
if id == shareID && data != nil {
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
}
}
return mount, unmountedShares, nil
}
// RejectReceivedShare handles DELETE Requests on /apps/files_sharing/api/v1/shares/{shareid}
@@ -166,15 +196,15 @@ func (h *Handler) RejectReceivedShare(w http.ResponseWriter, r *http.Request) {
Share: &collaboration.Share{
Id: &collaboration.ShareId{OpaqueId: shareID},
},
State: collaboration.ShareState_SHARE_STATE_REJECTED,
Hidden: h.getReceivedShareHideFlagFromShareID(r.Context(), shareID),
State: collaboration.ShareState_SHARE_STATE_REJECTED,
}
updateMask := &fieldmaskpb.FieldMask{Paths: []string{"state", "hidden"}}
updateMask := &fieldmaskpb.FieldMask{Paths: []string{"state"}}
data := h.updateReceivedShare(w, r, receivedShare, updateMask)
if data != nil {
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
data, meta, err := h.updateReceivedShare(r.Context(), receivedShare, updateMask)
if err != nil {
response.WriteOCSData(w, r, meta, nil, err)
}
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
}
func (h *Handler) UpdateReceivedShare(w http.ResponseWriter, r *http.Request) {
@@ -199,18 +229,17 @@ func (h *Handler) UpdateReceivedShare(w http.ResponseWriter, r *http.Request) {
rs, _ := getReceivedShareFromID(r.Context(), client, shareID)
if rs != nil && rs.Share != nil {
receivedShare.State = rs.Share.State
receivedShare.State = rs.State
}
data := h.updateReceivedShare(w, r, receivedShare, updateMask)
if data != nil {
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
data, meta, err := h.updateReceivedShare(r.Context(), receivedShare, updateMask)
if err != nil {
response.WriteOCSData(w, r, meta, nil, err)
}
// TODO: do we need error handling here?
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
}
func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, receivedShare *collaboration.ReceivedShare, fieldMask *fieldmaskpb.FieldMask) *conversions.ShareData {
ctx := r.Context()
func (h *Handler) updateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *fieldmaskpb.FieldMask) (*conversions.ShareData, response.Meta, error) {
logger := appctx.GetLogger(ctx)
updateShareRequest := &collaboration.UpdateReceivedShareRequest{
@@ -220,23 +249,19 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, re
client, err := h.getClient()
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err)
return nil
return nil, response.MetaServerError, errors.Wrap(err, "error getting grpc gateway client")
}
shareRes, err := client.UpdateReceivedShare(ctx, updateShareRequest)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", err)
return nil
return nil, response.MetaServerError, errors.Wrap(err, "grpc update received share request failed")
}
if shareRes.Status.Code != rpc.Code_CODE_OK {
if shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil)
return nil
return nil, response.MetaNotFound, errors.New(shareRes.Status.Message)
}
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", errors.Errorf("code: %d, message: %s", shareRes.Status.Code, shareRes.Status.Message))
return nil
return nil, response.MetaServerError, errors.Errorf("grpc update received share request failed: code: %d, message: %s", shareRes.Status.Code, shareRes.Status.Message)
}
rs := shareRes.GetShare()
@@ -244,27 +269,23 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, re
info, status, err := h.getResourceInfoByID(ctx, client, rs.Share.ResourceId)
if err != nil || status.Code != rpc.Code_CODE_OK {
h.logProblems(logger, status, err, "could not stat, skipping")
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc get resource info failed", errors.Errorf("code: %d, message: %s", status.Code, status.Message))
return nil
return nil, response.MetaServerError, errors.Errorf("grpc get resource info failed: code: %d, message: %s", status.Code, status.Message)
}
data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share)
if err != nil {
logger.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping")
}
data := conversions.CS3Share2ShareData(ctx, rs.Share)
data.State = mapState(rs.GetState())
data.Hidden = rs.GetHidden()
h.addFileInfo(ctx, data, info)
h.mapUserIds(r.Context(), client, data)
h.mapUserIds(ctx, client, data)
if data.State == ocsStateAccepted {
// Needed because received shares can be jailed in a folder in the users home
data.Path = path.Join(h.sharePrefix, path.Base(info.Path))
}
return data
return data, response.MetaOK, nil
}
func (h *Handler) updateReceivedFederatedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool) {
@@ -337,21 +358,8 @@ func (h *Handler) updateReceivedFederatedShare(w http.ResponseWriter, r *http.Re
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
}
// getReceivedShareHideFlagFromShareId returns the hide flag of a received share based on its ID.
func (h *Handler) getReceivedShareHideFlagFromShareID(ctx context.Context, shareID string) bool {
client, err := h.getClient()
if err != nil {
return false
}
rs, _ := getReceivedShareFromID(ctx, client, shareID)
if rs != nil {
return rs.GetShare().GetHidden()
}
return false
}
// getReceivedShareFromID uses a client to the gateway to fetch a share based on its ID.
func getReceivedShareFromID(ctx context.Context, client gateway.GatewayAPIClient, shareID string) (*collaboration.GetReceivedShareResponse, *response.Response) {
func getReceivedShareFromID(ctx context.Context, client gateway.GatewayAPIClient, shareID string) (*collaboration.ReceivedShare, *response.Response) {
s, err := client.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Id{
@@ -376,7 +384,7 @@ func getReceivedShareFromID(ctx context.Context, client gateway.GatewayAPIClient
return nil, arbitraryOcsResponse(response.MetaBadRequest.StatusCode, e.Error())
}
return s, nil
return s.Share, nil
}
// getSharedResource attempts to get a shared resource from the storage from the resource reference.
@@ -403,23 +411,17 @@ func getSharedResource(ctx context.Context, client gateway.GatewayAPIClient, res
return res, nil
}
// getSharedResource gets the list of all shares for the current user.
func getSharesList(ctx context.Context, client gateway.GatewayAPIClient) (*collaboration.ListReceivedSharesResponse, *response.Response) {
shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
// listReceivedShares list all received shares for the current user.
func listReceivedShares(ctx context.Context, client gateway.GatewayAPIClient) ([]*collaboration.ReceivedShare, error) {
res, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
if err != nil {
e := errors.Wrap(err, "error getting shares list")
return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error())
return nil, errtypes.InternalError("grpc list received shares request failed")
}
if shares.Status.Code != rpc.Code_CODE_OK {
if shares.Status.Code == rpc.Code_CODE_NOT_FOUND {
e := fmt.Errorf("not found")
return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error())
}
e := fmt.Errorf(shares.GetStatus().GetMessage())
return nil, arbitraryOcsResponse(response.MetaServerError.StatusCode, e.Error())
if err := errtypes.NewErrtypeFromStatus(res.Status); err != nil {
return nil, err
}
return shares, nil
return res.Shares, nil
}
// arbitraryOcsResponse abstracts the boilerplate that is creating a response.Response struct.

View File

@@ -303,11 +303,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) {
return
}
s, err := conversions.CS3Share2ShareData(ctx, share)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
s := conversions.CS3Share2ShareData(ctx, share)
h.addFileInfo(ctx, s, statRes.Info)
@@ -402,12 +398,12 @@ func (h *Handler) updateExistingShareMountpoints(ctx context.Context, shareType
}
granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.Token)
lrs, ocsResponse := getSharesList(granteeCtx, client)
if ocsResponse != nil {
return ocsResponse.OCS.Meta.StatusCode, ocsResponse.OCS.Meta.Message, nil
receivedShares, err := listReceivedShares(granteeCtx, client)
if err != nil {
return response.MetaServerError.StatusCode, "could not list shares", nil
}
for _, s := range lrs.Shares {
for _, s := range receivedShares {
if s.GetShare().GetId() != share.Id && s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED && utils.ResourceIDEqual(s.Share.ResourceId, info.GetId()) {
updateRequest := &collaboration.UpdateReceivedShareRequest{
Share: &collaboration.ReceivedShare{
@@ -595,12 +591,8 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) {
})
if err == nil && uRes.GetShare() != nil {
resourceID = uRes.Share.Share.ResourceId
share, err = conversions.CS3Share2ShareData(ctx, uRes.Share.Share)
share = conversions.CS3Share2ShareData(ctx, uRes.Share.Share)
share.Hidden = uRes.Share.Hidden
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
}
}
@@ -635,11 +627,7 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) {
if err == nil && uRes.GetShare() != nil {
resourceID = uRes.Share.ResourceId
share, err = conversions.CS3Share2ShareData(ctx, uRes.Share)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
share = conversions.CS3Share2ShareData(ctx, uRes.Share)
}
}
@@ -874,11 +862,7 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, share *col
h.statCache.RemoveStat(currentUser.Id, share.ResourceId)
}
resultshare, err := conversions.CS3Share2ShareData(ctx, uRes.Share)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
resultshare := conversions.CS3Share2ShareData(ctx, uRes.Share)
statReq := provider.StatRequest{Ref: &provider.Reference{
ResourceId: uRes.Share.ResourceId,
@@ -1072,11 +1056,7 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) {
}
}
data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share)
if err != nil {
sublog.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping")
continue
}
data := conversions.CS3Share2ShareData(r.Context(), rs.Share)
data.State = mapState(rs.GetState())
data.Hidden = rs.Hidden

View File

@@ -281,11 +281,7 @@ func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, share
},
}
data, err := conversions.CS3Share2ShareData(ctx, share)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "deleting share failed", err)
return
}
data := conversions.CS3Share2ShareData(ctx, share)
// A deleted share should not have an ID.
data.ID = ""
@@ -337,11 +333,7 @@ func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.Filte
// build OCS response payload
for _, s := range lsUserSharesResponse.Shares {
data, err := conversions.CS3Share2ShareData(ctx, s)
if err != nil {
log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping")
continue
}
data := conversions.CS3Share2ShareData(ctx, s)
info, status, err := h.getResourceInfoByID(ctx, client, s.ResourceId)
if err != nil || status.Code != rpc.Code_CODE_OK {

Some files were not shown because too many files have changed in this diff Show More