diff --git a/.drone.env b/.drone.env index f078382aa29..6558db87adf 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=901157f2b3d0ba1f36b1e4d22c8384e255094b11 +CORE_COMMITID=370fd807e464dcc5cb9619a8890107424254dfa6 CORE_BRANCH=master diff --git a/changelog/unreleased/appprovider-unsupported.md b/changelog/unreleased/appprovider-unsupported.md new file mode 100644 index 00000000000..271ccb4fa95 --- /dev/null +++ b/changelog/unreleased/appprovider-unsupported.md @@ -0,0 +1,5 @@ +Enhancement: fail initialization of a WOPI AppProvider if +the underlying app is not WOPI-compliant nor it is supported +by the WOPI bridge extensions + +https://github.com/cs3org/reva/pull/2034 diff --git a/changelog/unreleased/escape-ldap-filter.md b/changelog/unreleased/escape-ldap-filter.md new file mode 100644 index 00000000000..a1bad3ecdf4 --- /dev/null +++ b/changelog/unreleased/escape-ldap-filter.md @@ -0,0 +1,5 @@ +Enhancement: escape ldap filters + +Added ldap filter escaping to increase the security of reva. + +https://github.com/cs3org/reva/pull/2042 diff --git a/changelog/unreleased/fix-revad-logging.md b/changelog/unreleased/fix-revad-logging.md new file mode 100644 index 00000000000..fa397da9f3a --- /dev/null +++ b/changelog/unreleased/fix-revad-logging.md @@ -0,0 +1,8 @@ +Bugfix: Do not truncate logs on restart + +This change fixes the way log files were opened. +Before they were truncated and now the log file +will be open in append mode and created it if it +does not exist. + +https://github.com/cs3org/reva/pull/2047 diff --git a/changelog/unreleased/fix-storageid-shares.md b/changelog/unreleased/fix-storageid-shares.md new file mode 100644 index 00000000000..077cd32bb66 --- /dev/null +++ b/changelog/unreleased/fix-storageid-shares.md @@ -0,0 +1,5 @@ +Bugfix: Fix the storage id of shares + +The storageid in the share object contained an incorrect value. + +https://github.com/cs3org/reva/pull/2033 diff --git a/changelog/unreleased/nextcloud-user-backend.md b/changelog/unreleased/nextcloud-user-backend.md new file mode 100644 index 00000000000..190367be775 --- /dev/null +++ b/changelog/unreleased/nextcloud-user-backend.md @@ -0,0 +1,6 @@ +Enhancement: Nextcloud user backend + +Adds Nextcloud as a user backend (Nextcloud drivers for 'auth' and 'user'). +Also adds back the Nextcloud storage integration tests. + +https://github.com/cs3org/reva/pull/2043 \ No newline at end of file diff --git a/changelog/unreleased/sharing-filter-methods.md b/changelog/unreleased/sharing-filter-methods.md new file mode 100644 index 00000000000..7b90d29d742 --- /dev/null +++ b/changelog/unreleased/sharing-filter-methods.md @@ -0,0 +1,5 @@ +Enhancement: Add utility methods for creating share filters + +Updated the CS3 API to include the new share grantee filter and added utility methods for creating share filters. This will help making the code more concise. + +https://github.com/cs3org/reva/pull/2044 diff --git a/cmd/reva/ocm-share-list.go b/cmd/reva/ocm-share-list.go index 1bff9bcd3bc..ed246a8c26e 100644 --- a/cmd/reva/ocm-share-list.go +++ b/cmd/reva/ocm-share-list.go @@ -29,6 +29,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/ocm/share" "github.com/jedib0t/go-pretty/table" ) @@ -60,14 +61,7 @@ func ocmShareListCommand() *command { StorageId: tokens[0], OpaqueId: tokens[1], } - shareRequest.Filters = []*ocm.ListOCMSharesRequest_Filter{ - { - Type: ocm.ListOCMSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &ocm.ListOCMSharesRequest_Filter_ResourceId{ - ResourceId: id, - }, - }, - } + shareRequest.Filters = []*ocm.ListOCMSharesRequest_Filter{share.ResourceIDFilter(id)} } shareRes, err := shareClient.ListOCMShares(ctx, shareRequest) diff --git a/cmd/reva/public-share-list.go b/cmd/reva/public-share-list.go index 1a0a2f1a744..baa13e4ad5f 100644 --- a/cmd/reva/public-share-list.go +++ b/cmd/reva/public-share-list.go @@ -29,6 +29,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/publicshare" "github.com/jedib0t/go-pretty/table" ) @@ -60,14 +61,7 @@ func publicShareListCommand() *command { StorageId: tokens[0], OpaqueId: tokens[1], } - shareRequest.Filters = []*link.ListPublicSharesRequest_Filter{ - { - Type: link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &link.ListPublicSharesRequest_Filter_ResourceId{ - ResourceId: id, - }, - }, - } + shareRequest.Filters = []*link.ListPublicSharesRequest_Filter{publicshare.ResourceIDFilter(id)} } shareRes, err := shareClient.ListPublicShares(ctx, shareRequest) diff --git a/cmd/reva/share-list.go b/cmd/reva/share-list.go index 9ec94a5999e..fa00b1dc0b0 100644 --- a/cmd/reva/share-list.go +++ b/cmd/reva/share-list.go @@ -29,6 +29,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/share" "github.com/jedib0t/go-pretty/table" ) @@ -60,14 +61,7 @@ func shareListCommand() *command { StorageId: tokens[0], OpaqueId: tokens[1], } - shareRequest.Filters = []*collaboration.ListSharesRequest_Filter{ - { - Type: collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &collaboration.ListSharesRequest_Filter_ResourceId{ - ResourceId: id, - }, - }, - } + shareRequest.Filters = []*collaboration.Filter{share.ResourceIDFilter(id)} } shareRes, err := shareClient.ListShares(ctx, shareRequest) diff --git a/cmd/revad/runtime/runtime.go b/cmd/revad/runtime/runtime.go index 458caf401de..22d55eb30bd 100644 --- a/cmd/revad/runtime/runtime.go +++ b/cmd/revad/runtime/runtime.go @@ -235,7 +235,7 @@ func getWriter(out string) (io.Writer, error) { return os.Stdout, nil } - fd, err := os.Create(out) + fd, err := os.OpenFile(out, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { err = errors.Wrap(err, "error creating log file: "+out) return nil, err diff --git a/examples/nextcloud-integration/revad.toml b/examples/nextcloud-integration/revad.toml new file mode 100644 index 00000000000..6c9b0c06153 --- /dev/null +++ b/examples/nextcloud-integration/revad.toml @@ -0,0 +1,140 @@ +[shared] +gatewaysvc = "localhost:19000" + +[grpc] +address = "0.0.0.0:19000" + +[grpc.services.gateway] +authregistrysvc = "localhost:19000" +appprovidersvc = "localhost:19000" +appregistry = "localhost:19000" +storageregistrysvc = "localhost:19000" +preferencessvc = "localhost:19000" +userprovidersvc = "localhost:19000" +usershareprovidersvc = "localhost:19000" +publicshareprovidersvc = "localhost:19000" +ocmcoresvc = "localhost:19000" +ocmshareprovidersvc = "localhost:19000" +ocminvitemanagersvc = "localhost:19000" +ocmproviderauthorizersvc = "localhost:19000" +commit_share_to_storage_grant = false +datagateway = "http://localhost:19001/data" +transfer_expires = 6 # give it a moment + +[grpc.services.authregistry] +driver = "static" + +[grpc.services.authregistry.drivers.static.rules] +basic = "localhost:19000" + +[grpc.services.storageregistry] +driver = "static" + +[grpc.services.storageregistry.drivers.static] +home_provider = "/home" + +[grpc.services.storageregistry.drivers.static.rules] +"/home" = {"address" = "localhost:19000"} +"123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:19000"} + +[grpc.services.usershareprovider] +driver = "memory" + +[grpc.services.ocmcore] +driver = "json" + +# Note that ocmcore and ocmshareprovider should use the same file for storing the shares. +[grpc.services.ocmcore.drivers.json] +file = "/var/tmp/reva/shares_server_1.json" + +[grpc.services.ocminvitemanager] +driver = "json" + +[grpc.services.ocmshareprovider] +driver = "json" + +[grpc.services.ocmshareprovider.drivers.json] +file = "/var/tmp/reva/shares_server_1.json" + +[grpc.services.ocmproviderauthorizer] +driver = "json" + +[grpc.services.ocmproviderauthorizer.drivers.json] +providers = "/etc/revad/providers.json" + +[grpc.services.publicshareprovider] +driver = "memory" + +[grpc.services.appprovider] +driver = "demo" +iopsecret = "testsecret" +wopiurl = "http://0.0.0.0:8880/" +wopibridgeurl = "http://localhost:8000/wopib" + +[grpc.services.appregistry] +driver = "static" + +[grpc.services.appregistry.static.rules] +"text/plain" = "localhost:19000" +"text/markdown" = "localhost:19000" +"application/compressed-markdown" = "localhost:19000" +"application/vnd.oasis.opendocument.text" = "localhost:19000" +"application/vnd.oasis.opendocument.spreadsheet" = "localhost:19000" +"application/vnd.oasis.opendocument.presentation" = "localhost:19000" + +[grpc.services.storageprovider] +driver = "nextcloud" +mount_path = "/home" +mount_id = "123e4567-e89b-12d3-a456-426655440000" +expose_data_server = true +data_server_url = "http://127.0.0.1:19001/data" +enable_home_creation = true +disable_tus = true + +[grpc.services.storageprovider.mimetypes] +".zmd" = "application/compressed-markdown" + +[grpc.services.storageprovider.drivers.nextcloud] +end_point = "http://localhost/apps/sciencemesh/" +user_layout = "{{.Username}}" + + +[grpc.services.authprovider] +auth_manager = "nextcloud" +[grpc.services.authprovider.drivers.nextcloud] +end_point = "http://localhost/apps/sciencemesh/" + +[grpc.services.userprovider] +driver = "nextcloud" +[grpc.services.userprovider.drivers.nextcloud] +end_point = "http://localhost/apps/sciencemesh/" + +[http] +enabled_services = ["ocmd"] +enabled_middlewares = ["providerauthorizer", "cors"] +address = "0.0.0.0:19001" + +[http.services.dataprovider] +driver = "nextcloud" + +[http.services.prometheus] +[http.services.sysinfo] + +[http.services.dataprovider.drivers.localhome] +user_layout = "{{.Username}}" + +[http.services.ocmd] +prefix = "ocm" + +[http.middlewares.providerauthorizer] +driver = "json" + +[http.middlewares.providerauthorizer.drivers.json] +providers = "/etc/revad/providers.json" + +[http.services.ocs] +prefix = "ocs" + +[http.services.ocdav] + +[http.middlewares.cors] diff --git a/go.mod b/go.mod index cfad917fbec..25949f9a7d8 100644 --- a/go.mod +++ b/go.mod @@ -9,18 +9,18 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/ReneKroon/ttlcache/v2 v2.7.0 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/aws/aws-sdk-go v1.40.17 + github.com/aws/aws-sdk-go v1.40.37 github.com/beevik/etree v1.1.0 github.com/bluele/gcache v0.0.2 github.com/c-bata/go-prompt v0.2.5 github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8 + github.com/cs3org/go-cs3apis v0.0.0-20210906133842-03e4a408c1f3 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 - github.com/go-chi/chi/v5 v5.0.3 + github.com/go-chi/chi/v5 v5.0.4 github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-openapi/errors v0.20.0 // indirect github.com/go-openapi/strfmt v0.19.5 // indirect @@ -61,10 +61,10 @@ require ( go.mongodb.org/mongo-driver v1.5.1 // indirect go.opencensus.io v0.23.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0 - go.opentelemetry.io/otel v1.0.0-RC2 + go.opentelemetry.io/otel v1.0.0-RC3 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2 - go.opentelemetry.io/otel/sdk v1.0.0-RC2 - go.opentelemetry.io/otel/trace v1.0.0-RC2 + go.opentelemetry.io/otel/sdk v1.0.0-RC3 + go.opentelemetry.io/otel/trace v1.0.0-RC3 golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 diff --git a/go.sum b/go.sum index 69435978f26..2dff948d97d 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go v1.40.17 h1:WcE72YOL7ChzAWlgpEv9YMOqAwJDM1yzkv4GxWyS5wk= -github.com/aws/aws-sdk-go v1.40.17/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.40.37 h1:I+Q6cLctkFyMMrKukcDnj+i2kjrQ37LGiOM6xmsxC48= +github.com/aws/aws-sdk-go v1.40.37/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -92,6 +92,8 @@ github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJff github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8 h1:bqUkE0l5wD62TKH6HkbSVYwyYzZ0PIeak/hnW7ggUdU= github.com/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20210906133842-03e4a408c1f3 h1:NcLk09WK4wx/iIrEI+7ZFbr78APaRKJxF0+zl6kv4is= +github.com/cs3org/go-cs3apis v0.0.0-20210906133842-03e4a408c1f3/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -113,8 +115,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= -github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo= +github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -326,7 +328,6 @@ github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= @@ -485,16 +486,19 @@ go.opentelemetry.io/contrib v0.22.0 h1:0F7gDEjgb1WGn4ODIjaCAg75hmqF+UN0LiVgwxsCo go.opentelemetry.io/contrib v0.22.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0 h1:TjqELdtCtlOJQrTnXd2y+RP6wXKZUnnJer0HR0CSo18= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0/go.mod h1:KjqwX4uJNaj479ZjFpADOMJKOM4rBXq4kN7nbeuGKrY= -go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg= go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM= +go.opentelemetry.io/otel v1.0.0-RC3 h1:kvwiyEkiUT/JaadXzVLI/R1wDO934A7r3Bs2wEe6wqA= +go.opentelemetry.io/otel v1.0.0-RC3/go.mod h1:Ka5j3ua8tZs4Rkq4Ex3hwgBgOchyPVq5S6P2lz//nKQ= go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2 h1:RF0nWsIDpDBe+s06lkLxUw9CWQUAhO6hBSxxB7dz45s= go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2/go.mod h1:sZZqN3Vb0iT+NE6mZ1S7sNyH3t4PFk6ElK5TLGFBZ7E= go.opentelemetry.io/otel/oteltest v1.0.0-RC2 h1:xNKqMhlZYkASSyvF4JwObZFMq0jhFN3c3SP+2rCzVPk= go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= -go.opentelemetry.io/otel/sdk v1.0.0-RC2 h1:ROuteeSCBaZNjiT9JcFzZepmInDvLktR28Y6qKo8bCs= go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw= -go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w= +go.opentelemetry.io/otel/sdk v1.0.0-RC3 h1:iRMkET+EmJUn5mW0hJzygBraXRmrUwzbOtNvTCh/oKs= +go.opentelemetry.io/otel/sdk v1.0.0-RC3/go.mod h1:78H6hyg2fka0NYT9fqGuFLvly2yCxiBXDJAgLKo/2Us= go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= +go.opentelemetry.io/otel/trace v1.0.0-RC3 h1:9F0ayEvlxv8BmNmPbU005WK7hC+7KbOazCPZjNa1yME= +go.opentelemetry.io/otel/trace v1.0.0-RC3/go.mod h1:VUt2TUYd8S2/ZRX09ZDFZQwn2RqfMB5MzO17jBojGxo= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index 4130dacc77d..4a582a40c6a 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -187,7 +187,7 @@ func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShar } func (s *service) ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest) (*collaboration.ListReceivedSharesResponse, error) { - shares, err := s.sm.ListReceivedShares(ctx) // TODO(labkode): check what to update + shares, err := s.sm.ListReceivedShares(ctx, req.Filters) // TODO(labkode): check what to update if err != nil { return &collaboration.ListReceivedSharesResponse{ Status: status.NewInternal(ctx, err, "error listing received shares"), diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 31409e512d9..dd5ca8c6820 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -49,13 +49,19 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/share/cache" "github.com/cs3org/reva/pkg/share/cache/registry" "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) +const ( + storageIDPrefix string = "shared::" +) + // Handler implements the shares part of the ownCloud sharing API type Handler struct { gatewayAddr string @@ -607,7 +613,7 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { shares := make([]*conversions.ShareData, 0) - filters := []*collaboration.ListSharesRequest_Filter{} + filters := []*collaboration.Filter{} linkFilters := []*link.ListPublicSharesRequest_Filter{} var e error @@ -663,8 +669,8 @@ func (h *Handler) logProblems(s *rpc.Status, e error, msg string) { } } -func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix string) ([]*collaboration.ListSharesRequest_Filter, []*link.ListPublicSharesRequest_Filter, error) { - collaborationFilters := []*collaboration.ListSharesRequest_Filter{} +func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix string) ([]*collaboration.Filter, []*link.ListPublicSharesRequest_Filter, error) { + collaborationFilters := []*collaboration.Filter{} linkFilters := []*link.ListPublicSharesRequest_Filter{} ctx := r.Context() @@ -692,19 +698,9 @@ func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix stri return nil, nil, err } - collaborationFilters = append(collaborationFilters, &collaboration.ListSharesRequest_Filter{ - Type: collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &collaboration.ListSharesRequest_Filter_ResourceId{ - ResourceId: info.Id, - }, - }) + collaborationFilters = append(collaborationFilters, share.ResourceIDFilter(info.Id)) - linkFilters = append(linkFilters, &link.ListPublicSharesRequest_Filter{ - Type: link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &link.ListPublicSharesRequest_Filter_ResourceId{ - ResourceId: info.Id, - }, - }) + linkFilters = append(linkFilters, publicshare.ResourceIDFilter(info.Id)) return collaborationFilters, linkFilters, nil } @@ -732,7 +728,6 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf } s.MimeType = parsedMt // TODO STime: &types.Timestamp{Seconds: info.Mtime.Seconds, Nanos: info.Mtime.Nanos}, - s.StorageID = info.Id.StorageId + "!" + info.Id.OpaqueId // TODO Storage: int s.ItemSource = wrapResourceID(info.Id) s.FileSource = s.ItemSource @@ -742,6 +737,7 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf s.FileTarget = path.Join(h.sharePrefix, path.Base(info.Path)) } s.Path = path.Join("/", path.Base(info.Path)) // TODO hm this might have to be relative to the users home ... depends on the webdav_namespace config + s.StorageID = storageIDPrefix + s.FileTarget // TODO FileParent: // item type s.ItemType = conversions.ResourceType(info.GetType()).String() diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 1a133f34b6c..7ce8822d973 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -120,7 +120,7 @@ func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareI response.WriteOCSSuccess(w, r, nil) } -func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListSharesRequest_Filter) ([]*conversions.ShareData, *rpc.Status, error) { +func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.Filter) ([]*conversions.ShareData, *rpc.Status, error) { ctx := r.Context() log := appctx.GetLogger(ctx) diff --git a/pkg/app/provider/wopi/wopi.go b/pkg/app/provider/wopi/wopi.go index 0f731d76af3..1c7600c9400 100644 --- a/pkg/app/provider/wopi/wopi.go +++ b/pkg/app/provider/wopi/wopi.go @@ -136,9 +136,6 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc q.Add("fileid", resource.GetId().OpaqueId) q.Add("endpoint", resource.GetId().StorageId) q.Add("viewmode", viewMode.String()) - // TODO the folder URL should be resolved as e.g. `'https://cernbox.cern.ch/index.php/apps/files/?dir=' + filepath.Dir(req.Ref.GetPath())` - // or should be deprecated/removed altogether, needs discussion and decision. - // q.Add("folderurl", "...") u, ok := ctxpkg.ContextGetUser(ctx) if ok { // else defaults to "Anonymous Guest" q.Add("username", u.Username) @@ -287,17 +284,18 @@ func getAppURLs(c *config) (map[string]map[string]string, error) { // scrape app's home page to find the appname if !strings.Contains(buf.String(), c.AppName) { - // || (c.AppName != "CodiMD" && c.AppName != "Etherpad") { return nil, errors.New("Application server at " + c.AppURL + " does not match this AppProvider for " + c.AppName) } // register the supported mimetypes in the AppRegistry: this is hardcoded for the time being - if c.AppName == "CodiMD" { + switch c.AppName { + case "CodiMD": appURLs = getCodimdExtensions(c.AppURL) - } else if c.AppName == "Etherpad" { + case "Etherpad": appURLs = getEtherpadExtensions(c.AppURL) + default: + return nil, errors.New("Application server " + c.AppName + " running at " + c.AppURL + " is unsupported") } - } return appURLs, nil } @@ -370,7 +368,7 @@ func getCodimdExtensions(appURL string) map[string]map[string]string { func getEtherpadExtensions(appURL string) map[string]map[string]string { appURLs := make(map[string]map[string]string) appURLs["edit"] = map[string]string{ - ".etherpad": appURL, + ".epd": appURL, } return appURLs } diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index cefd1adef0c..b136a05f5db 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -244,5 +244,5 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) } func (am *mgr) getLoginFilter(login string) string { - return strings.ReplaceAll(am.c.LoginFilter, "{{login}}", login) + return strings.ReplaceAll(am.c.LoginFilter, "{{login}}", ldap.EscapeFilter(login)) } diff --git a/pkg/auth/manager/loader/loader.go b/pkg/auth/manager/loader/loader.go index 1c6c7eff15b..f2a756cb0ca 100644 --- a/pkg/auth/manager/loader/loader.go +++ b/pkg/auth/manager/loader/loader.go @@ -26,6 +26,7 @@ import ( _ "github.com/cs3org/reva/pkg/auth/manager/json" _ "github.com/cs3org/reva/pkg/auth/manager/ldap" _ "github.com/cs3org/reva/pkg/auth/manager/machine" + _ "github.com/cs3org/reva/pkg/auth/manager/nextcloud" _ "github.com/cs3org/reva/pkg/auth/manager/oidc" _ "github.com/cs3org/reva/pkg/auth/manager/publicshares" // Add your own here diff --git a/pkg/auth/manager/nextcloud/nextcloud.go b/pkg/auth/manager/nextcloud/nextcloud.go new file mode 100644 index 00000000000..98889346c5e --- /dev/null +++ b/pkg/auth/manager/nextcloud/nextcloud.go @@ -0,0 +1,152 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Package nextcloud verifies a clientID and clientSecret against a Nextcloud backend. +package nextcloud + +import ( + "context" + "encoding/json" + "io" + "net/http" + "strings" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/auth" + "github.com/cs3org/reva/pkg/auth/manager/registry" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +func init() { + registry.Register("nextcloud", New) +} + +type mgr struct { + client *http.Client + endPoint string +} + +type config struct { + EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` +} + +// Action describes a REST request to forward to the Nextcloud backend +type Action struct { + verb string + username string + argS string +} + +func (c *config) init() { +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} + +// New returns an auth manager implementation that verifies against a Nextcloud backend. +func New(m map[string]interface{}) (auth.Manager, error) { + c, err := parseConfig(m) + if err != nil { + return nil, err + } + c.init() + + return &mgr{ + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + client: &http.Client{}, + }, nil +} + +func (am *mgr) Configure(ml map[string]interface{}) error { + return nil +} + +func (am *mgr) do(ctx context.Context, a Action) (int, []byte, error) { + log := appctx.GetLogger(ctx) + // user, err := getUser(ctx) + // if err != nil { + // return 0, nil, err + // } + // url := am.endPoint + "~" + a.username + "/api/" + a.verb + url := "http://localhost/apps/sciencemesh/~" + a.username + "/api/" + a.verb + log.Info().Msgf("am.do %s %s", url, a.argS) + req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) + if err != nil { + return 0, nil, err + } + + req.Header.Set("Content-Type", "application/json") + resp, err := am.client.Do(req) + if err != nil { + return 0, nil, err + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, nil, err + } + + log.Info().Msgf("am.do response %d %s", resp.StatusCode, body) + return resp.StatusCode, body, nil +} + +// Authenticate method as defined in https://github.com/cs3org/reva/blob/28500a8/pkg/auth/auth.go#L31-L33 +func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { + var params = map[string]string{ + "password": clientSecret, + // "username": clientID, + } + bodyStr, err := json.Marshal(params) + if err != nil { + return nil, nil, err + } + log := appctx.GetLogger(ctx) + log.Info().Msgf("Authenticate %s %s", clientID, bodyStr) + + statusCode, _, err := am.do(ctx, Action{"Authenticate", clientID, string(bodyStr)}) + + if err != nil { + return nil, nil, err + } + + if statusCode != 200 { + return nil, nil, errors.New("Username/password not recognized by Nextcloud backend") + } + user := &user.User{ + Username: clientID, + Id: &user.UserId{ + OpaqueId: clientID, + Idp: "localhost", + Type: 1, + }, + Mail: clientID, + DisplayName: clientID, + Groups: nil, + } + return user, nil, nil +} diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index ffe2c05e953..05c4a7c5595 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -274,13 +274,13 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference return m.GetShare(ctx, ref) } -func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) { +func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { uid := conversions.FormatUserID(ctxpkg.ContextMustGetUser(ctx).Id) query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)" var filterQuery string params := []interface{}{uid, uid, 0, 1} for i, f := range filters { - if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { + if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { filterQuery += "(fileid_prefix=? AND item_source=?)" if i != len(filters)-1 { filterQuery += " AND " @@ -314,7 +314,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare } // we list the shares that are targeted to the user in context or to the user groups. -func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { +func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { user := ctxpkg.ContextMustGetUser(ctx) uid := conversions.FormatUserID(user.Id) diff --git a/pkg/group/manager/ldap/ldap.go b/pkg/group/manager/ldap/ldap.go index a41cec36257..196d608ade8 100644 --- a/pkg/group/manager/ldap/ldap.go +++ b/pkg/group/manager/ldap/ldap.go @@ -393,10 +393,10 @@ func (m *manager) getMemberFilter(gid *grouppb.GroupId) string { } func (m *manager) getAttributeFilter(attribute, value string) string { - attr := strings.ReplaceAll(m.c.AttributeFilter, "{{attr}}", attribute) - return strings.ReplaceAll(attr, "{{value}}", value) + attr := strings.ReplaceAll(m.c.AttributeFilter, "{{attr}}", ldap.EscapeFilter(attribute)) + return strings.ReplaceAll(attr, "{{value}}", ldap.EscapeFilter(value)) } func (m *manager) getFindFilter(query string) string { - return strings.ReplaceAll(m.c.FindFilter, "{{query}}", query) + return strings.ReplaceAll(m.c.FindFilter, "{{query}}", ldap.EscapeFilter(query)) } diff --git a/pkg/ocm/share/share.go b/pkg/ocm/share/share.go index 9eb439a6e04..e4b7d1188d6 100644 --- a/pkg/ocm/share/share.go +++ b/pkg/ocm/share/share.go @@ -55,3 +55,13 @@ type Manager interface { // UpdateReceivedShare updates the received share with share state. UpdateReceivedShare(ctx context.Context, ref *ocm.ShareReference, f *ocm.UpdateReceivedOCMShareRequest_UpdateField) (*ocm.ReceivedShare, error) } + +// ResourceIDFilter is an abstraction for creating filter by resource id. +func ResourceIDFilter(id *provider.ResourceId) *ocm.ListOCMSharesRequest_Filter { + return &ocm.ListOCMSharesRequest_Filter{ + Type: ocm.ListOCMSharesRequest_Filter_TYPE_RESOURCE_ID, + Term: &ocm.ListOCMSharesRequest_Filter_ResourceId{ + ResourceId: id, + }, + } +} diff --git a/pkg/publicshare/publicshare.go b/pkg/publicshare/publicshare.go index 0cab57363a0..fa00beb32ce 100644 --- a/pkg/publicshare/publicshare.go +++ b/pkg/publicshare/publicshare.go @@ -83,3 +83,13 @@ func AddSignature(share *link.PublicShare, pw string) error { } return nil } + +// ResourceIDFilter is an abstraction for creating filter by resource id. +func ResourceIDFilter(id *provider.ResourceId) *link.ListPublicSharesRequest_Filter { + return &link.ListPublicSharesRequest_Filter{ + Type: link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID, + Term: &link.ListPublicSharesRequest_Filter_ResourceId{ + ResourceId: id, + }, + } +} diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 79f3ae36460..fe47803f94b 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -354,7 +354,7 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference return nil, errtypes.NotFound(ref.String()) } -func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) { +func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { var ss []*collaboration.Share m.Lock() defer m.Unlock() @@ -368,7 +368,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare // check filters // TODO(labkode): add the rest of filters. for _, f := range filters { - if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { + if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { if utils.ResourceIDEqual(s.ResourceId, f.GetResourceId()) { ss = append(ss, s) } @@ -381,7 +381,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare } // we list the shares that are targeted to the user in context or to the user groups. -func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { +func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { var rss []*collaboration.ReceivedShare m.Lock() defer m.Unlock() diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index df7e2b9a8ed..8f84c77d9b2 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -215,7 +215,7 @@ func (m *manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer return nil, errtypes.NotFound(ref.String()) } -func (m *manager) ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) { +func (m *manager) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { var ss []*collaboration.Share m.lock.Lock() defer m.lock.Unlock() @@ -229,7 +229,7 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.ListS // check filters // TODO(labkode): add the rest of filters. for _, f := range filters { - if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { + if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { if utils.ResourceIDEqual(s.ResourceId, f.GetResourceId()) { ss = append(ss, s) } @@ -242,7 +242,7 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.ListS } // we list the shares that are targeted to the user in context or to the user groups. -func (m *manager) ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { +func (m *manager) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { var rss []*collaboration.ReceivedShare m.lock.Lock() defer m.lock.Unlock() diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index 6fea507391c..c43ef2b3778 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -269,13 +269,13 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference return m.GetShare(ctx, ref) } -func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) { +func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { uid := ctxpkg.ContextMustGetUser(ctx).Username query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)" var filterQuery string params := []interface{}{uid, uid, 0, 1} for i, f := range filters { - if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { + if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { filterQuery += "(item_source=?)" if i != len(filters)-1 { filterQuery += " AND " @@ -315,7 +315,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare } // we list the shares that are targeted to the user in context or to the user groups. -func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { +func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { user := ctxpkg.ContextMustGetUser(ctx) uid := user.Username diff --git a/pkg/share/manager/sql/sql_test.go b/pkg/share/manager/sql/sql_test.go index fd61639fa6c..aba406862a7 100644 --- a/pkg/share/manager/sql/sql_test.go +++ b/pkg/share/manager/sql/sql_test.go @@ -165,20 +165,15 @@ var _ = Describe("SQL manager", func() { Describe("ListShares", func() { It("lists shares", func() { - shares, err := mgr.ListShares(ctx, []*collaboration.ListSharesRequest_Filter{}) + shares, err := mgr.ListShares(ctx, []*collaboration.Filter{}) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(1)) - shares, err = mgr.ListShares(ctx, []*collaboration.ListSharesRequest_Filter{ - { - Type: collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID, - Term: &collaboration.ListSharesRequest_Filter_ResourceId{ - ResourceId: &provider.ResourceId{ - StorageId: "/", - OpaqueId: "somethingElse", - }, - }, - }, + shares, err = mgr.ListShares(ctx, []*collaboration.Filter{ + share.ResourceIDFilter(&provider.ResourceId{ + StorageId: "/", + OpaqueId: "somethingElse", + }), }) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(0)) @@ -188,7 +183,7 @@ var _ = Describe("SQL manager", func() { Describe("ListReceivedShares", func() { It("lists received shares", func() { loginAs(otherUser) - shares, err := mgr.ListReceivedShares(ctx) + shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(1)) }) @@ -230,7 +225,7 @@ var _ = Describe("SQL manager", func() { Describe("Unshare", func() { It("deletes shares", func() { loginAs(otherUser) - shares, err := mgr.ListReceivedShares(ctx) + shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(1)) @@ -243,7 +238,7 @@ var _ = Describe("SQL manager", func() { Expect(err).ToNot(HaveOccurred()) loginAs(otherUser) - shares, err = mgr.ListReceivedShares(ctx) + shares, err = mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(0)) }) diff --git a/pkg/share/share.go b/pkg/share/share.go index b66bdf8969d..9184e0d93c5 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -41,10 +41,10 @@ type Manager interface { // ListShares returns the shares created by the user. If md is provided is not nil, // it returns only shares attached to the given resource. - ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) + ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) // ListReceivedShares returns the list of shares the user has access. - ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) + ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) // GetReceivedShare returns the information for a received share the user has access. GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) @@ -52,3 +52,33 @@ type Manager interface { // UpdateReceivedShare updates the received share with share state. UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareReference, f *collaboration.UpdateReceivedShareRequest_UpdateField) (*collaboration.ReceivedShare, error) } + +// GroupGranteeFilter is an abstraction for creating filter by grantee type group. +func GroupGranteeFilter() *collaboration.Filter { + return &collaboration.Filter{ + Type: collaboration.Filter_TYPE_GRANTEE_TYPE, + Term: &collaboration.Filter_GranteeType{ + GranteeType: provider.GranteeType_GRANTEE_TYPE_GROUP, + }, + } +} + +// UserGranteeFilter is an abstraction for creating filter by grantee type user. +func UserGranteeFilter() *collaboration.Filter { + return &collaboration.Filter{ + Type: collaboration.Filter_TYPE_GRANTEE_TYPE, + Term: &collaboration.Filter_GranteeType{ + GranteeType: provider.GranteeType_GRANTEE_TYPE_USER, + }, + } +} + +// ResourceIDFilter is an abstraction for creating filter by resource id. +func ResourceIDFilter(id *provider.ResourceId) *collaboration.Filter { + return &collaboration.Filter{ + Type: collaboration.Filter_TYPE_RESOURCE_ID, + Term: &collaboration.Filter_ResourceId{ + ResourceId: id, + }, + } +} diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index b0b1e8aa581..4920679eba3 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -30,6 +30,8 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/registry" @@ -71,7 +73,7 @@ func New(m map[string]interface{}) (storage.FS, error) { return nil, err } - return NewStorageDriver(conf, nil) + return NewStorageDriver(conf) } // CreateStorageSpace creates a storage space @@ -80,12 +82,16 @@ func (nc *StorageDriver) CreateStorageSpace(ctx context.Context, req *provider.C } // NewStorageDriver returns a new NextcloudStorageDriver -func NewStorageDriver(c *StorageDriverConfig, client *http.Client) (*StorageDriver, error) { - if client == nil { +func NewStorageDriver(c *StorageDriverConfig) (*StorageDriver, error) { + var client *http.Client + if c.MockHTTP { + nextcloudServerMock := GetNextcloudServerMock() + client, _ = TestingHTTPClient(nextcloudServerMock) + } else { client = &http.Client{} } return &StorageDriver{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/~alice/" + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" client: client, }, nil } @@ -96,6 +102,15 @@ type Action struct { argS string } +func getUser(ctx context.Context) (*user.User, error) { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + err := errors.Wrap(errtypes.UserRequired(""), "nextcloud storage driver: error getting user from ctx") + return nil, err + } + return u, nil +} + // SetHTTPClient sets the HTTP client func (nc *StorageDriver) SetHTTPClient(c *http.Client) { nc.client = c @@ -125,33 +140,50 @@ func (nc *StorageDriver) doUpload(r io.ReadCloser) error { return err } -func (nc *StorageDriver) do(a Action, endPoint string) (int, []byte, error) { - url := endPoint + a.verb +func (nc *StorageDriver) do(ctx context.Context, a Action) (int, []byte, error) { + log := appctx.GetLogger(ctx) + user, err := getUser(ctx) + if err != nil { + return 0, nil, err + } + url := nc.endPoint + "~" + user.Username + "/api/" + a.verb + log.Info().Msgf("nc.do %s", url) req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) if err != nil { - panic(err) + return 0, nil, err } req.Header.Set("Content-Type", "application/json") resp, err := nc.client.Do(req) if err != nil { - panic(err) + return 0, nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - return resp.StatusCode, body, err + if err != nil { + return 0, nil, err + } + + log.Info().Msgf("nc.do response %d %s", resp.StatusCode, body) + return resp.StatusCode, body, nil } // GetHome as defined in the storage.FS interface func (nc *StorageDriver) GetHome(ctx context.Context) (string, error) { - _, respBody, err := nc.do(Action{"GetHome", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("GetHome") + + _, respBody, err := nc.do(ctx, Action{"GetHome", ""}) return string(respBody), err } // CreateHome as defined in the storage.FS interface func (nc *StorageDriver) CreateHome(ctx context.Context) error { - _, _, err := nc.do(Action{"CreateHome", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("CreateHome") + + _, _, err := nc.do(ctx, Action{"CreateHome", ""}) return err } @@ -161,7 +193,10 @@ func (nc *StorageDriver) CreateDir(ctx context.Context, ref *provider.Reference) if err != nil { return err } - _, _, err = nc.do(Action{"CreateDir", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("CreateDir %s", bodyStr) + + _, _, err = nc.do(ctx, Action{"CreateDir", string(bodyStr)}) return err } @@ -171,7 +206,10 @@ func (nc *StorageDriver) Delete(ctx context.Context, ref *provider.Reference) er if err != nil { return err } - _, _, err = nc.do(Action{"Delete", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("Delete %s", bodyStr) + + _, _, err = nc.do(ctx, Action{"Delete", string(bodyStr)}) return err } @@ -181,17 +219,23 @@ func (nc *StorageDriver) Move(ctx context.Context, oldRef, newRef *provider.Refe data["from"] = oldRef.Path data["to"] = newRef.Path bodyStr, _ := json.Marshal(data) - _, _, err := nc.do(Action{"Move", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("Move %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"Move", string(bodyStr)}) return err } // GetMD as defined in the storage.FS interface func (nc *StorageDriver) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) { bodyStr, err := json.Marshal(ref) + log := appctx.GetLogger(ctx) + log.Info().Msgf("GetMD %s", bodyStr) + if err != nil { return nil, err } - status, body, err := nc.do(Action{"GetMD", string(bodyStr)}, nc.endPoint) + status, body, err := nc.do(ctx, Action{"GetMD", string(bodyStr)}) if err != nil { return nil, err } @@ -242,10 +286,12 @@ func (nc *StorageDriver) GetMD(ctx context.Context, ref *provider.Reference, mdK // ListFolder as defined in the storage.FS interface func (nc *StorageDriver) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { bodyStr, err := json.Marshal(ref) + log := appctx.GetLogger(ctx) + log.Info().Msgf("LisfFolder %s", bodyStr) if err != nil { return nil, err } - status, body, err := nc.do(Action{"ListFolder", string(bodyStr)}, nc.endPoint) + status, body, err := nc.do(ctx, Action{"ListFolder", string(bodyStr)}) if err != nil { return nil, err } @@ -282,7 +328,10 @@ func (nc *StorageDriver) ListFolder(ctx context.Context, ref *provider.Reference // InitiateUpload as defined in the storage.FS interface func (nc *StorageDriver) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { bodyStr, _ := json.Marshal(ref) - _, respBody, err := nc.do(Action{"InitiateUpload", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("InitiateUpload %s", bodyStr) + + _, respBody, err := nc.do(ctx, Action{"InitiateUpload", string(bodyStr)}) if err != nil { return nil, err } @@ -297,25 +346,34 @@ func (nc *StorageDriver) InitiateUpload(ctx context.Context, ref *provider.Refer // Upload as defined in the storage.FS interface func (nc *StorageDriver) Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error { bodyStr, _ := json.Marshal(ref) + log := appctx.GetLogger(ctx) + log.Info().Msgf("Upload %s", bodyStr) + err := nc.doUpload(r) if err != nil { return err } - _, _, err = nc.do(Action{"Upload", string(bodyStr)}, nc.endPoint) + _, _, err = nc.do(ctx, Action{"Upload", string(bodyStr)}) return err } // Download as defined in the storage.FS interface func (nc *StorageDriver) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"Download", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("Download %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"Download", string(bodyStr)}) return nil, err } // ListRevisions as defined in the storage.FS interface func (nc *StorageDriver) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { bodyStr, _ := json.Marshal(ref) - _, respBody, err := nc.do(Action{"ListRevisions", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("ListRevisions %s", bodyStr) + + _, respBody, err := nc.do(ctx, Action{"ListRevisions", string(bodyStr)}) if err != nil { return nil, err } @@ -343,20 +401,29 @@ func (nc *StorageDriver) ListRevisions(ctx context.Context, ref *provider.Refere // DownloadRevision as defined in the storage.FS interface func (nc *StorageDriver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"DownloadRevision", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("DownloadRevision %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"DownloadRevision", string(bodyStr)}) return nil, err } // RestoreRevision as defined in the storage.FS interface func (nc *StorageDriver) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"RestoreRevision", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("RestoreRevision %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"RestoreRevision", string(bodyStr)}) return err } // ListRecycle as defined in the storage.FS interface func (nc *StorageDriver) ListRecycle(ctx context.Context, key string, path string) ([]*provider.RecycleItem, error) { - _, respBody, err := nc.do(Action{"ListRecycle", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("ListRecycle") + _, respBody, err := nc.do(ctx, Action{"ListRecycle", ""}) + if err != nil { return nil, err } @@ -391,62 +458,89 @@ func (nc *StorageDriver) ListRecycle(ctx context.Context, key string, path strin // RestoreRecycleItem as defined in the storage.FS interface func (nc *StorageDriver) RestoreRecycleItem(ctx context.Context, key string, path string, restoreRef *provider.Reference) error { bodyStr, _ := json.Marshal(restoreRef) - _, _, err := nc.do(Action{"RestoreRecycleItem", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("RestoreRecycleItem %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"RestoreRecycleItem", string(bodyStr)}) return err } // PurgeRecycleItem as defined in the storage.FS interface func (nc *StorageDriver) PurgeRecycleItem(ctx context.Context, key string, path string) error { bodyStr, _ := json.Marshal(key) - _, _, err := nc.do(Action{"PurgeRecycleItem", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("PurgeRecycleItem %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"PurgeRecycleItem", string(bodyStr)}) return err } // EmptyRecycle as defined in the storage.FS interface func (nc *StorageDriver) EmptyRecycle(ctx context.Context) error { - _, _, err := nc.do(Action{"EmptyRecycle", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("EmptyRecycle") + + _, _, err := nc.do(ctx, Action{"EmptyRecycle", ""}) return err } // GetPathByID as defined in the storage.FS interface func (nc *StorageDriver) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { bodyStr, _ := json.Marshal(id) - _, respBody, err := nc.do(Action{"GetPathByID", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("GetPathByID %s", bodyStr) + + _, respBody, err := nc.do(ctx, Action{"GetPathByID", string(bodyStr)}) return string(respBody), err } // AddGrant as defined in the storage.FS interface func (nc *StorageDriver) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"AddGrant", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("AggGrant %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"AddGrant", string(bodyStr)}) return err } // RemoveGrant as defined in the storage.FS interface func (nc *StorageDriver) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"RemoveGrant", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("RemoveGrant %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"RemoveGrant", string(bodyStr)}) return err } // DenyGrant as defined in the storage.FS interface func (nc *StorageDriver) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"DenyGrant", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("DenyGrant %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"DenyGrant", string(bodyStr)}) return err } // UpdateGrant as defined in the storage.FS interface func (nc *StorageDriver) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"UpdateGrant", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("UpdateGrant %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"UpdateGrant", string(bodyStr)}) return err } // ListGrants as defined in the storage.FS interface func (nc *StorageDriver) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { bodyStr, _ := json.Marshal(ref) - _, respBody, err := nc.do(Action{"ListGrants", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("ListGrants %s", bodyStr) + + _, respBody, err := nc.do(ctx, Action{"ListGrants", string(bodyStr)}) if err != nil { return nil, err } @@ -511,38 +605,56 @@ func (nc *StorageDriver) ListGrants(ctx context.Context, ref *provider.Reference // GetQuota as defined in the storage.FS interface func (nc *StorageDriver) GetQuota(ctx context.Context) (uint64, uint64, error) { - _, _, err := nc.do(Action{"GetQuota", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("GetQuota") + + _, _, err := nc.do(ctx, Action{"GetQuota", ""}) return 0, 0, err } // CreateReference as defined in the storage.FS interface func (nc *StorageDriver) CreateReference(ctx context.Context, path string, targetURI *url.URL) error { - _, _, err := nc.do(Action{"CreateReference", fmt.Sprintf(`{"path":"%s"}`, path)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("CreateReference %s", path) + + _, _, err := nc.do(ctx, Action{"CreateReference", fmt.Sprintf(`{"path":"%s"}`, path)}) return err } // Shutdown as defined in the storage.FS interface func (nc *StorageDriver) Shutdown(ctx context.Context) error { - _, _, err := nc.do(Action{"Shutdown", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("Shutdown") + + _, _, err := nc.do(ctx, Action{"Shutdown", ""}) return err } // SetArbitraryMetadata as defined in the storage.FS interface func (nc *StorageDriver) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error { bodyStr, _ := json.Marshal(md) - _, _, err := nc.do(Action{"SetArbitraryMetadata", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("SetArbitraryMetadata %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"SetArbitraryMetadata", string(bodyStr)}) return err } // UnsetArbitraryMetadata as defined in the storage.FS interface func (nc *StorageDriver) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error { bodyStr, _ := json.Marshal(ref) - _, _, err := nc.do(Action{"UnsetArbitraryMetadata", string(bodyStr)}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msgf("UnsetArbitraryMetadata %s", bodyStr) + + _, _, err := nc.do(ctx, Action{"UnsetArbitraryMetadata", string(bodyStr)}) return err } // ListStorageSpaces :as defined in the storage.FS interface func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { - _, _, err := nc.do(Action{"ListStorageSpaces", ""}, nc.endPoint) + log := appctx.GetLogger(ctx) + log.Info().Msg("ListStorageSpaces") + + _, _, err := nc.do(ctx, Action{"ListStorageSpaces", ""}) return nil, err } diff --git a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go index 1b438894e6c..a38420f41bc 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go +++ b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go @@ -19,9 +19,12 @@ package nextcloud import ( + "context" "fmt" "io" + "net" "net/http" + "net/http/httptest" "strings" ) @@ -49,86 +52,86 @@ const serverStateMetadata = "METADATA" var serverState = serverStateEmpty var responses = map[string]Response{ - `POST /apps/sciencemesh/~alice/AddGrant {"path":"/subdir"}`: {200, ``, serverStateGrantAdded}, + `POST /apps/sciencemesh/~einstein/api/AddGrant {"path":"/subdir"}`: {200, ``, serverStateGrantAdded}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/subdir"} EMPTY`: {200, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/subdir"} HOME`: {200, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/subdir"} NEWDIR`: {200, ``, serverStateSubdirNewdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/subdir"} EMPTY`: {200, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/subdir"} HOME`: {200, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/subdir"} NEWDIR`: {200, ``, serverStateSubdirNewdir}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/newdir"} EMPTY`: {200, ``, serverStateNewdir}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/newdir"} HOME`: {200, ``, serverStateNewdir}, - `POST /apps/sciencemesh/~alice/CreateDir {"path":"/newdir"} SUBDIR`: {200, ``, serverStateSubdirNewdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/newdir"} EMPTY`: {200, ``, serverStateNewdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/newdir"} HOME`: {200, ``, serverStateNewdir}, + `POST /apps/sciencemesh/~einstein/api/CreateDir {"path":"/newdir"} SUBDIR`: {200, ``, serverStateSubdirNewdir}, - `POST /apps/sciencemesh/~alice/CreateHome `: {200, ``, serverStateHome}, - `POST /apps/sciencemesh/~alice/CreateHome {}`: {200, ``, serverStateHome}, + `POST /apps/sciencemesh/~einstein/api/CreateHome `: {200, ``, serverStateHome}, + `POST /apps/sciencemesh/~einstein/api/CreateHome {}`: {200, ``, serverStateHome}, - `POST /apps/sciencemesh/~alice/CreateReference {"path":"/Shares/reference"}`: {200, `[]`, serverStateReference}, + `POST /apps/sciencemesh/~einstein/api/CreateReference {"path":"/Shares/reference"}`: {200, `[]`, serverStateReference}, - `POST /apps/sciencemesh/~alice/Delete {"path":"/subdir"}`: {200, ``, serverStateRecycle}, + `POST /apps/sciencemesh/~einstein/api/Delete {"path":"/subdir"}`: {200, ``, serverStateRecycle}, - `POST /apps/sciencemesh/~alice/EmptyRecycle `: {200, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/EmptyRecycle `: {200, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/"} EMPTY`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/"} HOME`: {200, `{ "size": 1 }`, serverStateHome}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/"} EMPTY`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/"} HOME`: {200, `{ "size": 1 }`, serverStateHome}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/newdir"} EMPTY`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/newdir"} HOME`: {404, ``, serverStateHome}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/newdir"} SUBDIR`: {404, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/newdir"} NEWDIR`: {200, `{ "size": 1 }`, serverStateNewdir}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/newdir"} SUBDIR-NEWDIR`: {200, `{ "size": 1 }`, serverStateSubdirNewdir}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/newdir"} EMPTY`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/newdir"} HOME`: {404, ``, serverStateHome}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/newdir"} SUBDIR`: {404, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/newdir"} NEWDIR`: {200, `{ "size": 1 }`, serverStateNewdir}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/newdir"} SUBDIR-NEWDIR`: {200, `{ "size": 1 }`, serverStateSubdirNewdir}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/new_subdir"}`: {200, `{ "size": 1 }`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/new_subdir"}`: {200, `{ "size": 1 }`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} EMPTY`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} HOME`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} NEWDIR`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} RECYCLE`: {404, ``, serverStateRecycle}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} SUBDIR`: {200, `{ "size": 1 }`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} SUBDIR-NEWDIR`: {200, `{ "size": 1 }`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdir"} METADATA`: {200, `{ "size": 1, "metadata": { "foo": "bar" } }`, serverStateMetadata}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} EMPTY`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} HOME`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} NEWDIR`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} RECYCLE`: {404, ``, serverStateRecycle}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} SUBDIR`: {200, `{ "size": 1 }`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} SUBDIR-NEWDIR`: {200, `{ "size": 1 }`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdir"} METADATA`: {200, `{ "size": 1, "metadata": { "foo": "bar" } }`, serverStateMetadata}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdirRestored"} EMPTY`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdirRestored"} RECYCLE`: {404, ``, serverStateRecycle}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdirRestored"} SUBDIR`: {404, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/subdirRestored"} FILE-RESTORED`: {200, `{ "size": 1 }`, serverStateFileRestored}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdirRestored"} EMPTY`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdirRestored"} RECYCLE`: {404, ``, serverStateRecycle}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdirRestored"} SUBDIR`: {404, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/subdirRestored"} FILE-RESTORED`: {200, `{ "size": 1 }`, serverStateFileRestored}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/versionedFile"} EMPTY`: {200, `{ "size": 2 }`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/GetMD {"path":"/versionedFile"} FILE-RESTORED`: {200, `{ "size": 1 }`, serverStateFileRestored}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/versionedFile"} EMPTY`: {200, `{ "size": 2 }`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetMD {"path":"/versionedFile"} FILE-RESTORED`: {200, `{ "size": 1 }`, serverStateFileRestored}, - `POST /apps/sciencemesh/~alice/GetPathByID {"storage_id":"00000000-0000-0000-0000-000000000000","opaque_id":"fileid-%2Fsubdir"}`: {200, "/subdir", serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/GetPathByID {"storage_id":"00000000-0000-0000-0000-000000000000","opaque_id":"fileid-%2Fsubdir"}`: {200, "/subdir", serverStateEmpty}, - `POST /apps/sciencemesh/~alice/InitiateUpload {"path":"/file"}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/InitiateUpload {"path":"/file"}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListFolder {"path":"/"}`: {200, `["/subdir"]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListFolder {"path":"/"}`: {200, `["/subdir"]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListFolder {"path":"/Shares"} EMPTY`: {404, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListFolder {"path":"/Shares"} SUBDIR`: {404, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/ListFolder {"path":"/Shares"} REFERENCE`: {200, `["reference"]`, serverStateReference}, + `POST /apps/sciencemesh/~einstein/api/ListFolder {"path":"/Shares"} EMPTY`: {404, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListFolder {"path":"/Shares"} SUBDIR`: {404, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/ListFolder {"path":"/Shares"} REFERENCE`: {200, `["reference"]`, serverStateReference}, - `POST /apps/sciencemesh/~alice/ListGrants {"path":"/subdir"} SUBDIR`: {200, `[]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListGrants {"path":"/subdir"} GRANT-ADDED`: {200, `[ { "stat": true, "move": true, "delete": false } ]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListGrants {"path":"/subdir"} GRANT-UPDATED`: {200, `[ { "stat": true, "move": true, "delete": true } ]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListGrants {"path":"/subdir"} SUBDIR`: {200, `[]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListGrants {"path":"/subdir"} GRANT-ADDED`: {200, `[ { "stat": true, "move": true, "delete": false } ]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListGrants {"path":"/subdir"} GRANT-UPDATED`: {200, `[ { "stat": true, "move": true, "delete": true } ]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListRecycle EMPTY`: {200, `[]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListRecycle RECYCLE`: {200, `["/subdir"]`, serverStateRecycle}, + `POST /apps/sciencemesh/~einstein/api/ListRecycle EMPTY`: {200, `[]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListRecycle RECYCLE`: {200, `["/subdir"]`, serverStateRecycle}, - `POST /apps/sciencemesh/~alice/ListRevisions {"path":"/versionedFile"} EMPTY`: {500, `[1]`, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/ListRevisions {"path":"/versionedFile"} FILE-RESTORED`: {500, `[1, 2]`, serverStateFileRestored}, + `POST /apps/sciencemesh/~einstein/api/ListRevisions {"path":"/versionedFile"} EMPTY`: {500, `[1]`, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/ListRevisions {"path":"/versionedFile"} FILE-RESTORED`: {500, `[1, 2]`, serverStateFileRestored}, - `POST /apps/sciencemesh/~alice/Move {"from":"/subdir","to":"/new_subdir"}`: {200, ``, serverStateEmpty}, + `POST /apps/sciencemesh/~einstein/api/Move {"from":"/subdir","to":"/new_subdir"}`: {200, ``, serverStateEmpty}, - `POST /apps/sciencemesh/~alice/RemoveGrant {"path":"/subdir"} GRANT-ADDED`: {200, ``, serverStateGrantUpdated}, + `POST /apps/sciencemesh/~einstein/api/RemoveGrant {"path":"/subdir"} GRANT-ADDED`: {200, ``, serverStateGrantUpdated}, - `POST /apps/sciencemesh/~alice/RestoreRecycleItem null`: {200, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/RestoreRecycleItem {"path":"/subdirRestored"}`: {200, ``, serverStateFileRestored}, + `POST /apps/sciencemesh/~einstein/api/RestoreRecycleItem null`: {200, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/RestoreRecycleItem {"path":"/subdirRestored"}`: {200, ``, serverStateFileRestored}, - `POST /apps/sciencemesh/~alice/RestoreRevision {"path":"/versionedFile"}`: {200, ``, serverStateFileRestored}, + `POST /apps/sciencemesh/~einstein/api/RestoreRevision {"path":"/versionedFile"}`: {200, ``, serverStateFileRestored}, - `POST /apps/sciencemesh/~alice/SetArbitraryMetadata {"metadata":{"foo":"bar"}}`: {200, ``, serverStateMetadata}, + `POST /apps/sciencemesh/~einstein/api/SetArbitraryMetadata {"metadata":{"foo":"bar"}}`: {200, ``, serverStateMetadata}, - `POST /apps/sciencemesh/~alice/UnsetArbitraryMetadata {"path":"/subdir"}`: {200, ``, serverStateSubdir}, + `POST /apps/sciencemesh/~einstein/api/UnsetArbitraryMetadata {"path":"/subdir"}`: {200, ``, serverStateSubdir}, - `POST /apps/sciencemesh/~alice/UpdateGrant {"path":"/subdir"}`: {200, ``, serverStateGrantUpdated}, + `POST /apps/sciencemesh/~einstein/api/UpdateGrant {"path":"/subdir"}`: {200, ``, serverStateGrantUpdated}, } // GetNextcloudServerMock returns a handler that pretends to be a remote Nextcloud server @@ -166,3 +169,23 @@ func GetNextcloudServerMock() http.Handler { } }) } + +// TestingHTTPClient thanks to https://itnext.io/how-to-stub-requests-to-remote-hosts-with-go-6c2c1db32bf2 +// Ideally, this function would live in tests/helpers, but +// if we put it there, it gets excluded by .dockerignore, and the +// Docker build fails (see https://github.com/cs3org/reva/issues/1999) +// So putting it here for now - open to suggestions if someone knows +// a better way to inject this. +func TestingHTTPClient(handler http.Handler) (*http.Client, func()) { + s := httptest.NewServer(handler) + + cli := &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, network, _ string) (net.Conn, error) { + return net.Dial(network, s.Listener.Addr().String()) + }, + }, + } + + return cli, s.Close +} diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index a8c83d8d171..3f990e5266c 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -23,7 +23,13 @@ import ( "net/http" "os" + "google.golang.org/grpc/metadata" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/storage/fs/nextcloud" + jwt "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/cs3org/reva/tests/helpers" . "github.com/onsi/ginkgo" @@ -32,11 +38,20 @@ import ( var _ = Describe("Nextcloud", func() { var ( + ctx context.Context options map[string]interface{} tmpRoot string + user = &userpb.User{ + Id: &userpb.UserId{ + Idp: "0.0.0.0:19000", + OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + Type: userpb.UserType_USER_TYPE_PRIMARY, + }, + } ) BeforeEach(func() { + var err error tmpRoot, err := helpers.TempDir("reva-unit-tests-*-root") Expect(err).ToNot(HaveOccurred()) @@ -45,6 +60,19 @@ var _ = Describe("Nextcloud", func() { "enable_home": true, "share_folder": "/Shares", } + + ctx = context.Background() + + // Add auth token + tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) + Expect(err).ToNot(HaveOccurred()) + scope, err := scope.AddOwnerScope(nil) + Expect(err).ToNot(HaveOccurred()) + t, err := tokenManager.MintToken(ctx, user, scope) + Expect(err).ToNot(HaveOccurred()) + ctx = ctxpkg.ContextSetToken(ctx, t) + ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, t) + ctx = ctxpkg.ContextSetUser(ctx, user) }) AfterEach(func() { @@ -61,12 +89,10 @@ var _ = Describe("Nextcloud", func() { }) Describe("CreateHome", func() { It("calls the CreateHome endpoint", func() { - nextcloudServerMock := nextcloud.GetNextcloudServerMock() - var client *http.Client - client, _ = helpers.TestingHTTPClient(nextcloudServerMock) nc, _ := nextcloud.NewStorageDriver(&nextcloud.StorageDriverConfig{ EndPoint: "http://mock.com", - }, client) + MockHTTP: true, + }) const ( okResponse = `{ @@ -82,10 +108,10 @@ var _ = Describe("Nextcloud", func() { panic(err) } }) - mock, teardown := helpers.TestingHTTPClient(h) + mock, teardown := nextcloud.TestingHTTPClient(h) defer teardown() nc.SetHTTPClient(mock) - err2 := nc.CreateHome(context.TODO()) + err2 := nc.CreateHome(ctx) Expect(err2).ToNot(HaveOccurred()) }) }) diff --git a/pkg/storage/fs/owncloud/owncloud_unix.go b/pkg/storage/fs/owncloud/owncloud_unix.go index 11c82f1f6cb..0bb88308c89 100755 --- a/pkg/storage/fs/owncloud/owncloud_unix.go +++ b/pkg/storage/fs/owncloud/owncloud_unix.go @@ -16,6 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +//go:build !windows // +build !windows package owncloud diff --git a/pkg/storage/fs/owncloud/owncloud_windows.go b/pkg/storage/fs/owncloud/owncloud_windows.go index 50023514807..69dbbeddb3a 100644 --- a/pkg/storage/fs/owncloud/owncloud_windows.go +++ b/pkg/storage/fs/owncloud/owncloud_windows.go @@ -16,6 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +//go:build windows // +build windows package owncloud diff --git a/pkg/storage/fs/owncloudsql/owncloudsql_unix.go b/pkg/storage/fs/owncloudsql/owncloudsql_unix.go index 9e5c1ad9ff9..a63f712df69 100755 --- a/pkg/storage/fs/owncloudsql/owncloudsql_unix.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql_unix.go @@ -16,6 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +//go:build !windows // +build !windows package owncloudsql diff --git a/pkg/storage/utils/decomposedfs/decomposedfs_unix.go b/pkg/storage/utils/decomposedfs/decomposedfs_unix.go index 3cc758ec1b7..d5b7507002e 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs_unix.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs_unix.go @@ -16,6 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +//go:build !windows // +build !windows package decomposedfs diff --git a/pkg/storage/utils/localfs/localfs_unix.go b/pkg/storage/utils/localfs/localfs_unix.go index abcf56553f9..29ea2224afd 100644 --- a/pkg/storage/utils/localfs/localfs_unix.go +++ b/pkg/storage/utils/localfs/localfs_unix.go @@ -16,6 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +//go:build !windows // +build !windows package localfs diff --git a/pkg/user/manager/ldap/ldap.go b/pkg/user/manager/ldap/ldap.go index 575bfcd0d3e..c2dcde670fe 100644 --- a/pkg/user/manager/ldap/ldap.go +++ b/pkg/user/manager/ldap/ldap.go @@ -424,12 +424,12 @@ func (m *manager) getUserFilter(uid *userpb.UserId) string { } func (m *manager) getAttributeFilter(attribute, value string) string { - attr := strings.ReplaceAll(m.c.AttributeFilter, "{{attr}}", attribute) - return strings.ReplaceAll(attr, "{{value}}", value) + attr := strings.ReplaceAll(m.c.AttributeFilter, "{{attr}}", ldap.EscapeFilter(attribute)) + return strings.ReplaceAll(attr, "{{value}}", ldap.EscapeFilter(value)) } func (m *manager) getFindFilter(query string) string { - return strings.ReplaceAll(m.c.FindFilter, "{{query}}", query) + return strings.ReplaceAll(m.c.FindFilter, "{{query}}", ldap.EscapeFilter(query)) } func (m *manager) getGroupFilter(uid *userpb.UserId) string { diff --git a/pkg/user/manager/loader/loader.go b/pkg/user/manager/loader/loader.go index 41e61a9a6f5..7640c3ff313 100644 --- a/pkg/user/manager/loader/loader.go +++ b/pkg/user/manager/loader/loader.go @@ -23,6 +23,7 @@ import ( _ "github.com/cs3org/reva/pkg/user/manager/demo" _ "github.com/cs3org/reva/pkg/user/manager/json" _ "github.com/cs3org/reva/pkg/user/manager/ldap" + _ "github.com/cs3org/reva/pkg/user/manager/nextcloud" _ "github.com/cs3org/reva/pkg/user/manager/owncloudsql" // Add your own here ) diff --git a/pkg/user/manager/nextcloud/nextcloud.go b/pkg/user/manager/nextcloud/nextcloud.go new file mode 100644 index 00000000000..08219fdbda1 --- /dev/null +++ b/pkg/user/manager/nextcloud/nextcloud.go @@ -0,0 +1,155 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package nextcloud + +import ( + "context" + "io" + "net/http" + "strings" + + "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/user/manager/registry" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + // "github.com/cs3org/reva/pkg/errtypes" +) + +func init() { + registry.Register("nextcloud", New) +} + +type manager struct { + client *http.Client + endPoint string +} + +type config struct { + EndPoint string `mapstructure:"end_point"` + MockHTTP bool `mapstructure:"mock_http"` +} + +func (c *config) init() { + if c.EndPoint == "" { + c.EndPoint = "http://localhost/end/point?" + } +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + c.init() + return c, nil +} + +// Action describes a REST request to forward to the Nextcloud backend +type Action struct { + verb string + argS string +} + +// New returns a user manager implementation that reads a json file to provide user metadata. +func New(m map[string]interface{}) (user.Manager, error) { + c, err := parseConfig(m) + if err != nil { + return nil, err + } + + return &manager{ + client: &http.Client{}, + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + }, nil +} + +func (m *manager) do(a Action) (int, []byte, error) { + url := m.endPoint + a.verb + req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) + if err != nil { + panic(err) + } + + req.Header.Set("Content-Type", "application/json") + resp, err := m.client.Do(req) + if err != nil { + panic(err) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + return resp.StatusCode, body, err +} + +func (m *manager) Configure(ml map[string]interface{}) error { + return nil +} + +func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { + _, respBody, err := m.do(Action{"GetUser", uid.Idp}) + u := &userpb.User{ + Username: string(respBody), + } + return u, err +} + +func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { + _, respBody, err := m.do(Action{"FindUsers", query}) + u := &userpb.User{ + Username: string(respBody), + } + var us = make([]*userpb.User, 1) + us[0] = u + return us, err +} + +func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { + _, respBody, err := m.do(Action{"GetUserByClaim", value}) + u := &userpb.User{ + Username: string(respBody), + } + return u, err +} + +// func extractClaim(u *userpb.User, claim string) (string, error) { +// _, respBody, err := m.do(Action{"ExtractClaim", claim}) +// u := &userpb.User{ +// Username: string(respBody), +// } +// return u, err +// } + +// func userContains(u *userpb.User, query string) bool { +// _, respBody, err := m.do(Action{"userContains", query}) +// u := &userpb.User{ +// Username: string(respBody), +// } +// return u, err +// return false +// } + +func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { + _, respBody, err := m.do(Action{"GetUserGroups", uid.Idp}) + var gs = make([]string, 1) + gs[0] = string(respBody) + return gs, err +} diff --git a/pkg/user/manager/registry/registry.go b/pkg/user/manager/registry/registry.go index f9f2477a413..b436e0da0cf 100644 --- a/pkg/user/manager/registry/registry.go +++ b/pkg/user/manager/registry/registry.go @@ -18,7 +18,11 @@ package registry -import "github.com/cs3org/reva/pkg/user" +import ( + "fmt" + + "github.com/cs3org/reva/pkg/user" +) // NewFunc is the function that user managers // should register at init time. @@ -30,5 +34,6 @@ var NewFuncs = map[string]NewFunc{} // Register registers a new user manager new function. // Not safe for concurrent use. Safe for use from package init. func Register(name string, f NewFunc) { + fmt.Printf("Got registration for user manager %s\n", name) NewFuncs[name] = f } diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 25ba737c357..844697ad18a 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -327,7 +327,6 @@ File and sync features in a shared scenario #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) -- [apiShareManagementToShares/acceptShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L82) - [apiShareManagementToShares/acceptShares.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L207) - [apiShareManagementToShares/acceptShares.feature:261](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L261) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 423d8aa226a..cbd5ac6dde2 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -330,7 +330,6 @@ File and sync features in a shared scenario #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) -- [apiShareManagementToShares/acceptShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L82) - [apiShareManagementToShares/acceptShares.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L207) - [apiShareManagementToShares/acceptShares.feature:261](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L261) diff --git a/tests/helpers/helpers.go b/tests/helpers/helpers.go index a7dc7c9bb00..6b6ba372486 100644 --- a/tests/helpers/helpers.go +++ b/tests/helpers/helpers.go @@ -19,11 +19,7 @@ package helpers import ( - "context" "io/ioutil" - "net" - "net/http" - "net/http/httptest" "os" "path/filepath" "runtime" @@ -48,18 +44,3 @@ func TempDir(name string) (string, error) { return tmpRoot, nil } - -// TestingHTTPClient thanks to https://itnext.io/how-to-stub-requests-to-remote-hosts-with-go-6c2c1db32bf2 -func TestingHTTPClient(handler http.Handler) (*http.Client, func()) { - s := httptest.NewServer(handler) - - cli := &http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, network, _ string) (net.Conn, error) { - return net.Dial(network, s.Listener.Addr().String()) - }, - }, - } - - return cli, s.Close -} diff --git a/tests/integration/grpc/fixtures/storageprovider-nextcloud.toml b/tests/integration/grpc/fixtures/storageprovider-nextcloud.toml index 99ae4c85bdd..26d3b127ba1 100644 --- a/tests/integration/grpc/fixtures/storageprovider-nextcloud.toml +++ b/tests/integration/grpc/fixtures/storageprovider-nextcloud.toml @@ -5,5 +5,5 @@ address = "{{grpc_address}}" driver = "nextcloud" [grpc.services.storageprovider.drivers.nextcloud] -end_point = "http://localhost:8080/apps/sciencemesh/~alice/" +end_point = "http://localhost:8080/apps/sciencemesh/" mock_http = true diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index fc09c751d4f..9c19da71cd1 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -61,6 +61,7 @@ var _ = Describe("storage providers", func() { OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", Type: userpb.UserType_USER_TYPE_PRIMARY, }, + Username: "einstein", } homeRef = &storagep.Reference{Path: "/"} @@ -465,7 +466,7 @@ var _ = Describe("storage providers", func() { }) } - PDescribe("nextcloud", func() { + Describe("nextcloud", func() { BeforeEach(func() { dependencies = map[string]string{ "storage": "storageprovider-nextcloud.toml",